forge-openclaw-plugin 0.2.4 → 0.2.10

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.
Files changed (114) hide show
  1. package/README.md +186 -6
  2. package/dist/assets/board-C_m78kvK.js +6 -0
  3. package/dist/assets/board-C_m78kvK.js.map +1 -0
  4. package/dist/assets/favicon-BCHm9dUV.ico +0 -0
  5. package/dist/assets/index-BWtLtXwb.js +36 -0
  6. package/dist/assets/index-BWtLtXwb.js.map +1 -0
  7. package/dist/assets/index-Dp5GXY_z.css +1 -0
  8. package/dist/assets/motion-CpZvZumD.js +10 -0
  9. package/dist/assets/motion-CpZvZumD.js.map +1 -0
  10. package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  11. package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  12. package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  13. package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
  14. package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
  15. package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
  16. package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  17. package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  18. package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  19. package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
  20. package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
  21. package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  22. package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  23. package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
  24. package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
  25. package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  26. package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  27. package/dist/assets/table-DtyXTw03.js +23 -0
  28. package/dist/assets/table-DtyXTw03.js.map +1 -0
  29. package/dist/assets/ui-BXbpiKyS.js +46 -0
  30. package/dist/assets/ui-BXbpiKyS.js.map +1 -0
  31. package/dist/assets/vendor-CRS-psbw.css +1 -0
  32. package/dist/assets/vendor-QBH6qVEe.js +433 -0
  33. package/dist/assets/vendor-QBH6qVEe.js.map +1 -0
  34. package/dist/assets/viz-w-IMeueL.js +34 -0
  35. package/dist/assets/viz-w-IMeueL.js.map +1 -0
  36. package/dist/favicon.ico +0 -0
  37. package/dist/favicon.png +0 -0
  38. package/dist/index.html +29 -0
  39. package/dist/openclaw/api-client.d.ts +9 -0
  40. package/dist/openclaw/api-client.js +31 -4
  41. package/dist/openclaw/local-runtime.d.ts +3 -0
  42. package/dist/openclaw/local-runtime.js +136 -0
  43. package/dist/openclaw/parity.d.ts +4 -4
  44. package/dist/openclaw/parity.js +23 -33
  45. package/dist/openclaw/plugin-entry-shared.d.ts +4 -2
  46. package/dist/openclaw/plugin-entry-shared.js +63 -9
  47. package/dist/openclaw/routes.d.ts +12 -3
  48. package/dist/openclaw/routes.js +156 -924
  49. package/dist/openclaw/tools.js +242 -1100
  50. package/dist/server/app.js +2487 -0
  51. package/dist/server/db.js +313 -0
  52. package/dist/server/demo-data.js +49 -0
  53. package/dist/server/e2e-server.js +20 -0
  54. package/dist/server/errors.js +15 -0
  55. package/dist/server/index.js +16 -0
  56. package/dist/server/managers/base.js +17 -0
  57. package/dist/server/managers/contracts.js +47 -0
  58. package/dist/server/managers/platform/api-gateway-manager.js +11 -0
  59. package/dist/server/managers/platform/audit-manager.js +15 -0
  60. package/dist/server/managers/platform/authentication-manager.js +56 -0
  61. package/dist/server/managers/platform/authorization-manager.js +56 -0
  62. package/dist/server/managers/platform/background-job-manager.js +10 -0
  63. package/dist/server/managers/platform/configuration-manager.js +33 -0
  64. package/dist/server/managers/platform/database-manager.js +14 -0
  65. package/dist/server/managers/platform/event-bus-manager.js +7 -0
  66. package/dist/server/managers/platform/external-service-manager.js +11 -0
  67. package/dist/server/managers/platform/health-manager.js +7 -0
  68. package/dist/server/managers/platform/migration-manager.js +8 -0
  69. package/dist/server/managers/platform/search-index-manager.js +4 -0
  70. package/dist/server/managers/platform/secrets-manager.js +19 -0
  71. package/dist/server/managers/platform/session-manager.js +121 -0
  72. package/dist/server/managers/platform/storage-manager.js +16 -0
  73. package/dist/server/managers/platform/token-manager.js +37 -0
  74. package/dist/server/managers/platform/transaction-manager.js +8 -0
  75. package/dist/server/managers/platform/trusted-network.js +39 -0
  76. package/dist/server/managers/runtime.js +56 -0
  77. package/dist/server/managers/type-guards.js +4 -0
  78. package/dist/server/openapi.js +3553 -0
  79. package/dist/server/psyche-types.js +366 -0
  80. package/dist/server/repositories/activity-events.js +157 -0
  81. package/dist/server/repositories/collaboration.js +497 -0
  82. package/dist/server/repositories/deleted-entities.js +226 -0
  83. package/dist/server/repositories/domains.js +30 -0
  84. package/dist/server/repositories/event-log.js +64 -0
  85. package/dist/server/repositories/goals.js +156 -0
  86. package/dist/server/repositories/notes.js +359 -0
  87. package/dist/server/repositories/projects.js +211 -0
  88. package/dist/server/repositories/psyche.js +1353 -0
  89. package/dist/server/repositories/rewards.js +675 -0
  90. package/dist/server/repositories/settings.js +399 -0
  91. package/dist/server/repositories/tags.js +160 -0
  92. package/dist/server/repositories/task-runs.js +490 -0
  93. package/dist/server/repositories/tasks.js +424 -0
  94. package/dist/server/seed-demo.js +11 -0
  95. package/dist/server/services/context.js +214 -0
  96. package/dist/server/services/dashboard.js +173 -0
  97. package/dist/server/services/entity-crud.js +573 -0
  98. package/dist/server/services/gamification.js +215 -0
  99. package/dist/server/services/insights.js +91 -0
  100. package/dist/server/services/projects.js +77 -0
  101. package/dist/server/services/psyche.js +63 -0
  102. package/dist/server/services/relations.js +28 -0
  103. package/dist/server/services/reviews.js +88 -0
  104. package/dist/server/services/run-recovery.js +13 -0
  105. package/dist/server/services/tagging.js +49 -0
  106. package/dist/server/services/task-run-watchdog.js +92 -0
  107. package/dist/server/services/work-time.js +176 -0
  108. package/dist/server/types.js +1058 -0
  109. package/dist/server/web.js +91 -0
  110. package/openclaw.plugin.json +32 -9
  111. package/package.json +17 -4
  112. package/server/migrations/001_core.sql +411 -0
  113. package/server/migrations/002_psyche.sql +392 -0
  114. package/skills/forge-openclaw/SKILL.md +197 -271
@@ -0,0 +1,497 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { getDatabase } from "../db.js";
3
+ import { filterDeletedEntities, isEntityDeleted } from "./deleted-entities.js";
4
+ import { recordActivityEvent } from "./activity-events.js";
5
+ import { recordEventLog } from "./event-log.js";
6
+ import { createProject } from "./projects.js";
7
+ import { createTask } from "./tasks.js";
8
+ import { recordInsightAppliedReward } from "./rewards.js";
9
+ import { agentActionSchema, approvalRequestSchema, createAgentActionSchema, createInsightFeedbackSchema, createInsightSchema, insightFeedbackSchema, insightSchema, updateInsightSchema } from "../types.js";
10
+ function mapInsight(row) {
11
+ return insightSchema.parse({
12
+ id: row.id,
13
+ originType: row.origin_type,
14
+ originAgentId: row.origin_agent_id,
15
+ originLabel: row.origin_label,
16
+ visibility: row.visibility,
17
+ status: row.status,
18
+ entityType: row.entity_type,
19
+ entityId: row.entity_id,
20
+ timeframeLabel: row.timeframe_label,
21
+ title: row.title,
22
+ summary: row.summary,
23
+ recommendation: row.recommendation,
24
+ rationale: row.rationale,
25
+ confidence: row.confidence,
26
+ ctaLabel: row.cta_label,
27
+ evidence: JSON.parse(row.evidence_json),
28
+ createdAt: row.created_at,
29
+ updatedAt: row.updated_at
30
+ });
31
+ }
32
+ function mapFeedback(row) {
33
+ return insightFeedbackSchema.parse({
34
+ id: row.id,
35
+ insightId: row.insight_id,
36
+ actor: row.actor,
37
+ feedbackType: row.feedback_type,
38
+ note: row.note,
39
+ createdAt: row.created_at
40
+ });
41
+ }
42
+ function mapApproval(row) {
43
+ return approvalRequestSchema.parse({
44
+ id: row.id,
45
+ actionType: row.action_type,
46
+ status: row.status,
47
+ title: row.title,
48
+ summary: row.summary,
49
+ entityType: row.entity_type,
50
+ entityId: row.entity_id,
51
+ requestedByAgentId: row.requested_by_agent_id,
52
+ requestedByTokenId: row.requested_by_token_id,
53
+ requestedPayload: JSON.parse(row.requested_payload_json),
54
+ approvedBy: row.approved_by,
55
+ approvedAt: row.approved_at,
56
+ rejectedBy: row.rejected_by,
57
+ rejectedAt: row.rejected_at,
58
+ resolutionNote: row.resolution_note,
59
+ createdAt: row.created_at,
60
+ updatedAt: row.updated_at
61
+ });
62
+ }
63
+ function mapAction(row) {
64
+ return agentActionSchema.parse({
65
+ id: row.id,
66
+ agentId: row.agent_id,
67
+ tokenId: row.token_id,
68
+ actionType: row.action_type,
69
+ riskLevel: row.risk_level,
70
+ status: row.status,
71
+ title: row.title,
72
+ summary: row.summary,
73
+ payload: JSON.parse(row.payload_json),
74
+ idempotencyKey: row.idempotency_key,
75
+ approvalRequestId: row.approval_request_id,
76
+ outcome: JSON.parse(row.outcome_json),
77
+ createdAt: row.created_at,
78
+ updatedAt: row.updated_at,
79
+ completedAt: row.completed_at
80
+ });
81
+ }
82
+ function shouldRequireApproval(input, token) {
83
+ if (!token) {
84
+ return input.riskLevel !== "low";
85
+ }
86
+ if (token.autonomyMode === "approval_required") {
87
+ return true;
88
+ }
89
+ if (input.riskLevel === "high") {
90
+ return token.approvalMode !== "none";
91
+ }
92
+ if (token.approvalMode === "approval_by_default") {
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ function getInsightRowById(insightId) {
98
+ return getDatabase()
99
+ .prepare(`SELECT
100
+ id, origin_type, origin_agent_id, origin_label, visibility, status, entity_type, entity_id,
101
+ timeframe_label, title, summary, recommendation, rationale, confidence, cta_label, evidence_json, created_at, updated_at
102
+ FROM insights
103
+ WHERE id = ?`)
104
+ .get(insightId);
105
+ }
106
+ export function listInsights(filters = {}) {
107
+ const whereClauses = [];
108
+ const params = [];
109
+ if (filters.entityType) {
110
+ whereClauses.push("entity_type = ?");
111
+ params.push(filters.entityType);
112
+ }
113
+ if (filters.entityId) {
114
+ whereClauses.push("entity_id = ?");
115
+ params.push(filters.entityId);
116
+ }
117
+ if (filters.status) {
118
+ whereClauses.push("status = ?");
119
+ params.push(filters.status);
120
+ }
121
+ const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
122
+ const limitSql = filters.limit ? "LIMIT ?" : "";
123
+ if (filters.limit) {
124
+ params.push(filters.limit);
125
+ }
126
+ const rows = getDatabase()
127
+ .prepare(`SELECT
128
+ id, origin_type, origin_agent_id, origin_label, visibility, status, entity_type, entity_id,
129
+ timeframe_label, title, summary, recommendation, rationale, confidence, cta_label, evidence_json, created_at, updated_at
130
+ FROM insights
131
+ ${whereSql}
132
+ ORDER BY created_at DESC
133
+ ${limitSql}`)
134
+ .all(...params);
135
+ return filterDeletedEntities("insight", rows.map(mapInsight));
136
+ }
137
+ export function getInsightById(insightId) {
138
+ if (isEntityDeleted("insight", insightId)) {
139
+ return undefined;
140
+ }
141
+ const row = getInsightRowById(insightId);
142
+ return row ? mapInsight(row) : undefined;
143
+ }
144
+ export function createInsight(input, context) {
145
+ const parsed = createInsightSchema.parse(input);
146
+ const now = new Date().toISOString();
147
+ const insightId = `ins_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
148
+ getDatabase()
149
+ .prepare(`INSERT INTO insights (
150
+ id, origin_type, origin_agent_id, origin_label, visibility, status, entity_type, entity_id, timeframe_label,
151
+ title, summary, recommendation, rationale, confidence, cta_label, evidence_json, created_at, updated_at
152
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
153
+ .run(insightId, parsed.originType, parsed.originAgentId, parsed.originLabel, parsed.visibility, parsed.status, parsed.entityType, parsed.entityId, parsed.timeframeLabel, parsed.title, parsed.summary, parsed.recommendation, parsed.rationale, parsed.confidence, parsed.ctaLabel, JSON.stringify(parsed.evidence), now, now);
154
+ recordActivityEvent({
155
+ entityType: "insight",
156
+ entityId: insightId,
157
+ eventType: "insight_created",
158
+ title: `Insight captured: ${parsed.title}`,
159
+ description: parsed.summary,
160
+ actor: context.actor ?? null,
161
+ source: context.source,
162
+ metadata: {
163
+ originType: parsed.originType,
164
+ entityType: parsed.entityType ?? "",
165
+ entityId: parsed.entityId ?? ""
166
+ }
167
+ });
168
+ recordEventLog({
169
+ eventKind: "insight.created",
170
+ entityType: "insight",
171
+ entityId: insightId,
172
+ actor: context.actor ?? null,
173
+ source: context.source,
174
+ metadata: {
175
+ originType: parsed.originType,
176
+ confidence: parsed.confidence
177
+ }
178
+ });
179
+ return getInsightById(insightId);
180
+ }
181
+ export function updateInsight(insightId, input, context) {
182
+ const current = getInsightById(insightId);
183
+ if (!current) {
184
+ return undefined;
185
+ }
186
+ const parsed = updateInsightSchema.parse(input);
187
+ const updatedAt = new Date().toISOString();
188
+ getDatabase()
189
+ .prepare(`UPDATE insights
190
+ SET visibility = ?, status = ?, entity_type = ?, entity_id = ?, timeframe_label = ?, title = ?, summary = ?,
191
+ recommendation = ?, rationale = ?, confidence = ?, cta_label = ?, evidence_json = ?, updated_at = ?
192
+ WHERE id = ?`)
193
+ .run(parsed.visibility ?? current.visibility, parsed.status ?? current.status, parsed.entityType === undefined ? current.entityType : parsed.entityType, parsed.entityId === undefined ? current.entityId : parsed.entityId, parsed.timeframeLabel === undefined ? current.timeframeLabel : parsed.timeframeLabel, parsed.title ?? current.title, parsed.summary ?? current.summary, parsed.recommendation ?? current.recommendation, parsed.rationale ?? current.rationale, parsed.confidence ?? current.confidence, parsed.ctaLabel ?? current.ctaLabel, JSON.stringify(parsed.evidence ?? current.evidence), updatedAt, insightId);
194
+ recordEventLog({
195
+ eventKind: "insight.updated",
196
+ entityType: "insight",
197
+ entityId: insightId,
198
+ actor: context.actor ?? null,
199
+ source: context.source
200
+ });
201
+ return getInsightById(insightId);
202
+ }
203
+ export function deleteInsight(insightId, context) {
204
+ const existing = getInsightRowById(insightId);
205
+ if (!existing) {
206
+ return undefined;
207
+ }
208
+ getDatabase().prepare(`DELETE FROM insight_feedback WHERE insight_id = ?`).run(insightId);
209
+ getDatabase().prepare(`DELETE FROM insights WHERE id = ?`).run(insightId);
210
+ recordActivityEvent({
211
+ entityType: "insight",
212
+ entityId: insightId,
213
+ eventType: "insight_deleted",
214
+ title: `Insight deleted: ${existing.title}`,
215
+ description: existing.summary,
216
+ actor: context.actor ?? null,
217
+ source: context.source,
218
+ metadata: {
219
+ entityType: existing.entity_type ?? "",
220
+ entityId: existing.entity_id ?? ""
221
+ }
222
+ });
223
+ recordEventLog({
224
+ eventKind: "insight.deleted",
225
+ entityType: "insight",
226
+ entityId: insightId,
227
+ actor: context.actor ?? null,
228
+ source: context.source
229
+ });
230
+ return mapInsight(existing);
231
+ }
232
+ export function listInsightFeedback(insightId) {
233
+ const rows = getDatabase()
234
+ .prepare(`SELECT id, insight_id, actor, feedback_type, note, created_at
235
+ FROM insight_feedback
236
+ WHERE insight_id = ?
237
+ ORDER BY created_at DESC`)
238
+ .all(insightId);
239
+ return rows.map(mapFeedback);
240
+ }
241
+ export function createInsightFeedback(insightId, input, context) {
242
+ const insight = getInsightById(insightId);
243
+ if (!insight) {
244
+ return undefined;
245
+ }
246
+ const parsed = createInsightFeedbackSchema.parse(input);
247
+ const feedbackId = `fbk_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
248
+ const createdAt = new Date().toISOString();
249
+ getDatabase()
250
+ .prepare(`INSERT INTO insight_feedback (id, insight_id, actor, feedback_type, note, created_at) VALUES (?, ?, ?, ?, ?, ?)`)
251
+ .run(feedbackId, insightId, parsed.actor ?? context.actor ?? null, parsed.feedbackType, parsed.note, createdAt);
252
+ const nextStatus = parsed.feedbackType === "accepted"
253
+ ? "accepted"
254
+ : parsed.feedbackType === "dismissed"
255
+ ? "dismissed"
256
+ : parsed.feedbackType === "applied"
257
+ ? "applied"
258
+ : "snoozed";
259
+ getDatabase().prepare(`UPDATE insights SET status = ?, updated_at = ? WHERE id = ?`).run(nextStatus, createdAt, insightId);
260
+ if (parsed.feedbackType === "applied" && insight.entityType && insight.entityId) {
261
+ recordInsightAppliedReward(insightId, insight.entityType, insight.entityId, {
262
+ actor: parsed.actor ?? context.actor ?? null,
263
+ source: context.source
264
+ });
265
+ }
266
+ recordActivityEvent({
267
+ entityType: "insight",
268
+ entityId: insightId,
269
+ eventType: "insight_feedback_recorded",
270
+ title: `Insight ${nextStatus}: ${insight.title}`,
271
+ description: parsed.note || `Insight marked ${nextStatus}.`,
272
+ actor: parsed.actor ?? context.actor ?? null,
273
+ source: context.source,
274
+ metadata: {
275
+ feedbackType: parsed.feedbackType
276
+ }
277
+ });
278
+ return listInsightFeedback(insightId)[0];
279
+ }
280
+ function insertApprovalRequest(input) {
281
+ const approvalId = `apr_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
282
+ const now = new Date().toISOString();
283
+ getDatabase()
284
+ .prepare(`INSERT INTO approval_requests (
285
+ id, action_type, status, title, summary, entity_type, entity_id, requested_by_agent_id, requested_by_token_id,
286
+ requested_payload_json, resolution_note, created_at, updated_at
287
+ ) VALUES (?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?, '', ?, ?)`)
288
+ .run(approvalId, input.actionType, input.title, input.summary, input.entityType ?? null, input.entityId ?? null, input.requestedByAgentId ?? null, input.requestedByTokenId ?? null, JSON.stringify(input.requestedPayload), now, now);
289
+ recordEventLog({
290
+ eventKind: "approval.requested",
291
+ entityType: "approval_request",
292
+ entityId: approvalId,
293
+ actor: null,
294
+ source: "agent",
295
+ metadata: {
296
+ actionType: input.actionType
297
+ }
298
+ });
299
+ return listApprovalRequests().find((entry) => entry.id === approvalId);
300
+ }
301
+ export function listApprovalRequests(status) {
302
+ const rows = getDatabase()
303
+ .prepare(`SELECT
304
+ id, action_type, status, title, summary, entity_type, entity_id, requested_by_agent_id, requested_by_token_id,
305
+ requested_payload_json, approved_by, approved_at, rejected_by, rejected_at, resolution_note, created_at, updated_at
306
+ FROM approval_requests
307
+ ${status ? "WHERE status = ?" : ""}
308
+ ORDER BY created_at DESC`)
309
+ .all(...(status ? [status] : []));
310
+ return rows.map(mapApproval);
311
+ }
312
+ function getApprovalRequestRow(id) {
313
+ return getDatabase()
314
+ .prepare(`SELECT
315
+ id, action_type, status, title, summary, entity_type, entity_id, requested_by_agent_id, requested_by_token_id,
316
+ requested_payload_json, approved_by, approved_at, rejected_by, rejected_at, resolution_note, created_at, updated_at
317
+ FROM approval_requests
318
+ WHERE id = ?`)
319
+ .get(id);
320
+ }
321
+ function executeAgentAction(action, context) {
322
+ if (action.actionType === "create_insight") {
323
+ const insight = createInsight(createInsightSchema.parse(action.payload), context);
324
+ return { insightId: insight.id };
325
+ }
326
+ if (action.actionType === "create_task") {
327
+ const task = createTask(action.payload, { source: context.source, actor: context.actor ?? null });
328
+ return { taskId: task.id };
329
+ }
330
+ if (action.actionType === "create_project") {
331
+ const project = createProject(action.payload, { source: context.source, actor: context.actor ?? null });
332
+ return { projectId: project.id };
333
+ }
334
+ return { deferred: true };
335
+ }
336
+ export function createAgentAction(input, context, idempotencyKey) {
337
+ const parsed = createAgentActionSchema.parse(input);
338
+ if (idempotencyKey) {
339
+ const existing = getDatabase()
340
+ .prepare(`SELECT
341
+ id, agent_id, token_id, action_type, risk_level, status, title, summary, payload_json, idempotency_key,
342
+ approval_request_id, outcome_json, created_at, updated_at, completed_at
343
+ FROM agent_actions
344
+ WHERE idempotency_key = ?`)
345
+ .get(idempotencyKey);
346
+ if (existing) {
347
+ return {
348
+ action: mapAction(existing),
349
+ approvalRequest: existing.approval_request_id
350
+ ? listApprovalRequests().find((entry) => entry.id === existing.approval_request_id) ?? null
351
+ : null
352
+ };
353
+ }
354
+ }
355
+ const now = new Date().toISOString();
356
+ const actionId = `act_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
357
+ const requiresApproval = shouldRequireApproval(parsed, context.token);
358
+ const approvalRequest = requiresApproval
359
+ ? insertApprovalRequest({
360
+ actionType: parsed.actionType,
361
+ title: parsed.title,
362
+ summary: parsed.summary,
363
+ entityType: typeof parsed.payload.entityType === "string" ? parsed.payload.entityType : null,
364
+ entityId: typeof parsed.payload.entityId === "string" ? parsed.payload.entityId : null,
365
+ requestedByAgentId: parsed.agentId ?? context.token?.agentId ?? null,
366
+ requestedByTokenId: parsed.tokenId ?? context.token?.id ?? null,
367
+ requestedPayload: parsed.payload
368
+ })
369
+ : null;
370
+ const status = requiresApproval ? "pending_approval" : "executed";
371
+ const actionSeed = agentActionSchema.parse({
372
+ id: actionId,
373
+ agentId: parsed.agentId ?? context.token?.agentId ?? null,
374
+ tokenId: parsed.tokenId ?? context.token?.id ?? null,
375
+ actionType: parsed.actionType,
376
+ riskLevel: parsed.riskLevel,
377
+ status,
378
+ title: parsed.title,
379
+ summary: parsed.summary,
380
+ payload: parsed.payload,
381
+ idempotencyKey: idempotencyKey ?? null,
382
+ approvalRequestId: approvalRequest?.id ?? null,
383
+ outcome: {},
384
+ createdAt: now,
385
+ updatedAt: now,
386
+ completedAt: requiresApproval ? null : now
387
+ });
388
+ const outcome = requiresApproval ? {} : executeAgentAction(actionSeed, context);
389
+ getDatabase()
390
+ .prepare(`INSERT INTO agent_actions (
391
+ id, agent_id, token_id, action_type, risk_level, status, title, summary, payload_json, idempotency_key,
392
+ approval_request_id, outcome_json, created_at, updated_at, completed_at
393
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
394
+ .run(actionId, actionSeed.agentId, actionSeed.tokenId, actionSeed.actionType, actionSeed.riskLevel, actionSeed.status, actionSeed.title, actionSeed.summary, JSON.stringify(actionSeed.payload), actionSeed.idempotencyKey, actionSeed.approvalRequestId, JSON.stringify(outcome), actionSeed.createdAt, actionSeed.updatedAt, actionSeed.completedAt);
395
+ recordActivityEvent({
396
+ entityType: "agent_action",
397
+ entityId: actionId,
398
+ eventType: requiresApproval ? "agent_action_requested" : "agent_action_executed",
399
+ title: requiresApproval ? `Approval requested: ${parsed.title}` : `Agent action executed: ${parsed.title}`,
400
+ description: parsed.summary,
401
+ actor: context.actor ?? null,
402
+ source: context.source,
403
+ metadata: {
404
+ actionType: parsed.actionType,
405
+ riskLevel: parsed.riskLevel
406
+ }
407
+ });
408
+ const actionRow = getDatabase()
409
+ .prepare(`SELECT
410
+ id, agent_id, token_id, action_type, risk_level, status, title, summary, payload_json, idempotency_key,
411
+ approval_request_id, outcome_json, created_at, updated_at, completed_at
412
+ FROM agent_actions
413
+ WHERE id = ?`)
414
+ .get(actionId);
415
+ return { action: mapAction(actionRow), approvalRequest };
416
+ }
417
+ export function listAgentActions(agentId) {
418
+ const rows = getDatabase()
419
+ .prepare(`SELECT
420
+ id, agent_id, token_id, action_type, risk_level, status, title, summary, payload_json, idempotency_key,
421
+ approval_request_id, outcome_json, created_at, updated_at, completed_at
422
+ FROM agent_actions
423
+ WHERE agent_id = ?
424
+ ORDER BY created_at DESC`)
425
+ .all(agentId);
426
+ return rows.map(mapAction);
427
+ }
428
+ export function approveApprovalRequest(approvalId, note, actor) {
429
+ const row = getApprovalRequestRow(approvalId);
430
+ if (!row || row.status !== "pending") {
431
+ return row ? mapApproval(row) : undefined;
432
+ }
433
+ const approvedAt = new Date().toISOString();
434
+ getDatabase()
435
+ .prepare(`UPDATE approval_requests
436
+ SET status = 'approved', approved_by = ?, approved_at = ?, resolution_note = ?, updated_at = ?
437
+ WHERE id = ?`)
438
+ .run(actor, approvedAt, note, approvedAt, approvalId);
439
+ const actionRow = getDatabase()
440
+ .prepare(`SELECT
441
+ id, agent_id, token_id, action_type, risk_level, status, title, summary, payload_json, idempotency_key,
442
+ approval_request_id, outcome_json, created_at, updated_at, completed_at
443
+ FROM agent_actions
444
+ WHERE approval_request_id = ?`)
445
+ .get(approvalId);
446
+ if (actionRow) {
447
+ const action = mapAction(actionRow);
448
+ const outcome = executeAgentAction(action, {
449
+ actor,
450
+ source: "ui",
451
+ token: null
452
+ });
453
+ getDatabase()
454
+ .prepare(`UPDATE agent_actions
455
+ SET status = 'executed', outcome_json = ?, updated_at = ?, completed_at = ?
456
+ WHERE id = ?`)
457
+ .run(JSON.stringify(outcome), approvedAt, approvedAt, action.id);
458
+ getDatabase().prepare(`UPDATE approval_requests SET status = 'executed', updated_at = ? WHERE id = ?`).run(approvedAt, approvalId);
459
+ }
460
+ recordActivityEvent({
461
+ entityType: "approval_request",
462
+ entityId: approvalId,
463
+ eventType: "approval_request_approved",
464
+ title: `Approval request approved`,
465
+ description: note || "A pending agent action was approved.",
466
+ actor,
467
+ source: "ui",
468
+ metadata: {}
469
+ });
470
+ return mapApproval(getApprovalRequestRow(approvalId));
471
+ }
472
+ export function rejectApprovalRequest(approvalId, note, actor) {
473
+ const row = getApprovalRequestRow(approvalId);
474
+ if (!row || row.status !== "pending") {
475
+ return row ? mapApproval(row) : undefined;
476
+ }
477
+ const rejectedAt = new Date().toISOString();
478
+ getDatabase()
479
+ .prepare(`UPDATE approval_requests
480
+ SET status = 'rejected', rejected_by = ?, rejected_at = ?, resolution_note = ?, updated_at = ?
481
+ WHERE id = ?`)
482
+ .run(actor, rejectedAt, note, rejectedAt, approvalId);
483
+ getDatabase()
484
+ .prepare(`UPDATE agent_actions SET status = 'rejected', updated_at = ? WHERE approval_request_id = ?`)
485
+ .run(rejectedAt, approvalId);
486
+ recordActivityEvent({
487
+ entityType: "approval_request",
488
+ entityId: approvalId,
489
+ eventType: "approval_request_rejected",
490
+ title: `Approval request rejected`,
491
+ description: note || "A pending agent action was rejected.",
492
+ actor,
493
+ source: "ui",
494
+ metadata: {}
495
+ });
496
+ return mapApproval(getApprovalRequestRow(approvalId));
497
+ }