forge-openclaw-plugin 0.2.4 → 0.2.7
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 +113 -5
- package/dist/assets/board-CzgvdLO8.js +6 -0
- package/dist/assets/board-CzgvdLO8.js.map +1 -0
- package/dist/assets/favicon-BCHm9dUV.ico +0 -0
- package/dist/assets/index-8d_oM8fL.js +27 -0
- package/dist/assets/index-8d_oM8fL.js.map +1 -0
- package/dist/assets/index-D4A_bq8m.css +1 -0
- package/dist/assets/motion-STUd1O46.js +10 -0
- package/dist/assets/motion-STUd1O46.js.map +1 -0
- package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
- package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
- package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/assets/table-CtNlETLc.js +23 -0
- package/dist/assets/table-CtNlETLc.js.map +1 -0
- package/dist/assets/ui-ThzkR_oW.js +46 -0
- package/dist/assets/ui-ThzkR_oW.js.map +1 -0
- package/dist/assets/vendor-CRS-psbw.css +1 -0
- package/dist/assets/vendor-DyHAI6nk.js +423 -0
- package/dist/assets/vendor-DyHAI6nk.js.map +1 -0
- package/dist/assets/viz-BJuBCz_G.js +34 -0
- package/dist/assets/viz-BJuBCz_G.js.map +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +29 -0
- package/dist/openclaw/api-client.d.ts +8 -0
- package/dist/openclaw/api-client.js +31 -4
- package/dist/openclaw/local-runtime.d.ts +3 -0
- package/dist/openclaw/local-runtime.js +135 -0
- package/dist/openclaw/parity.d.ts +4 -4
- package/dist/openclaw/parity.js +23 -33
- package/dist/openclaw/plugin-entry-shared.d.ts +4 -2
- package/dist/openclaw/plugin-entry-shared.js +51 -9
- package/dist/openclaw/routes.d.ts +12 -3
- package/dist/openclaw/routes.js +156 -924
- package/dist/openclaw/tools.js +242 -1100
- package/dist/server/app.js +2450 -0
- package/dist/server/db.js +313 -0
- package/dist/server/e2e-server.js +20 -0
- package/dist/server/errors.js +15 -0
- package/dist/server/index.js +16 -0
- package/dist/server/managers/base.js +17 -0
- package/dist/server/managers/contracts.js +47 -0
- package/dist/server/managers/platform/api-gateway-manager.js +11 -0
- package/dist/server/managers/platform/audit-manager.js +15 -0
- package/dist/server/managers/platform/authentication-manager.js +56 -0
- package/dist/server/managers/platform/authorization-manager.js +56 -0
- package/dist/server/managers/platform/background-job-manager.js +10 -0
- package/dist/server/managers/platform/configuration-manager.js +33 -0
- package/dist/server/managers/platform/database-manager.js +14 -0
- package/dist/server/managers/platform/event-bus-manager.js +7 -0
- package/dist/server/managers/platform/external-service-manager.js +11 -0
- package/dist/server/managers/platform/health-manager.js +7 -0
- package/dist/server/managers/platform/migration-manager.js +8 -0
- package/dist/server/managers/platform/search-index-manager.js +4 -0
- package/dist/server/managers/platform/secrets-manager.js +19 -0
- package/dist/server/managers/platform/session-manager.js +121 -0
- package/dist/server/managers/platform/storage-manager.js +16 -0
- package/dist/server/managers/platform/token-manager.js +37 -0
- package/dist/server/managers/platform/transaction-manager.js +8 -0
- package/dist/server/managers/platform/trusted-network.js +39 -0
- package/dist/server/managers/runtime.js +56 -0
- package/dist/server/managers/type-guards.js +4 -0
- package/dist/server/openapi.js +3512 -0
- package/dist/server/psyche-types.js +395 -0
- package/dist/server/repositories/activity-events.js +157 -0
- package/dist/server/repositories/collaboration.js +497 -0
- package/dist/server/repositories/comments.js +176 -0
- package/dist/server/repositories/deleted-entities.js +192 -0
- package/dist/server/repositories/domains.js +30 -0
- package/dist/server/repositories/event-log.js +64 -0
- package/dist/server/repositories/goals.js +159 -0
- package/dist/server/repositories/projects.js +214 -0
- package/dist/server/repositories/psyche.js +1356 -0
- package/dist/server/repositories/rewards.js +675 -0
- package/dist/server/repositories/settings.js +399 -0
- package/dist/server/repositories/tags.js +160 -0
- package/dist/server/repositories/task-runs.js +488 -0
- package/dist/server/repositories/tasks.js +413 -0
- package/dist/server/services/context.js +214 -0
- package/dist/server/services/dashboard.js +170 -0
- package/dist/server/services/entity-crud.js +576 -0
- package/dist/server/services/gamification.js +215 -0
- package/dist/server/services/insights.js +91 -0
- package/dist/server/services/projects.js +75 -0
- package/dist/server/services/psyche.js +63 -0
- package/dist/server/services/relations.js +28 -0
- package/dist/server/services/reviews.js +88 -0
- package/dist/server/services/run-recovery.js +13 -0
- package/dist/server/services/tagging.js +49 -0
- package/dist/server/services/task-run-watchdog.js +92 -0
- package/dist/server/services/work-time.js +176 -0
- package/dist/server/types.js +999 -0
- package/dist/server/web.js +91 -0
- package/openclaw.plugin.json +21 -9
- package/package.json +17 -4
- package/server/migrations/001_core.sql +333 -0
- package/server/migrations/002_psyche.sql +241 -0
- package/server/migrations/003_timer_execution.sql +18 -0
- package/server/migrations/004_psyche_linked_entities.sql +5 -0
- package/server/migrations/005_adaptive_schemas.sql +157 -0
- package/server/migrations/006_psyche_auth_setting.sql +4 -0
- package/server/migrations/007_deleted_entities.sql +16 -0
- package/skills/forge-openclaw/SKILL.md +189 -275
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
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 { commentSchema, commentsListQuerySchema, createCommentSchema, updateCommentSchema } from "../psyche-types.js";
|
|
7
|
+
import { activityEntityTypeSchema } from "../types.js";
|
|
8
|
+
function mapComment(row) {
|
|
9
|
+
return commentSchema.parse({
|
|
10
|
+
id: row.id,
|
|
11
|
+
entityType: row.entity_type,
|
|
12
|
+
entityId: row.entity_id,
|
|
13
|
+
anchorKey: row.anchor_key,
|
|
14
|
+
body: row.body,
|
|
15
|
+
author: row.author,
|
|
16
|
+
source: row.source,
|
|
17
|
+
createdAt: row.created_at,
|
|
18
|
+
updatedAt: row.updated_at
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function getCommentRow(commentId) {
|
|
22
|
+
return getDatabase()
|
|
23
|
+
.prepare(`SELECT id, entity_type, entity_id, anchor_key, body, author, source, created_at, updated_at
|
|
24
|
+
FROM entity_comments
|
|
25
|
+
WHERE id = ?`)
|
|
26
|
+
.get(commentId);
|
|
27
|
+
}
|
|
28
|
+
export function getCommentById(commentId) {
|
|
29
|
+
if (isEntityDeleted("comment", commentId)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const row = getCommentRow(commentId);
|
|
33
|
+
return row ? mapComment(row) : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function listComments(query = {}) {
|
|
36
|
+
const parsed = commentsListQuerySchema.parse(query);
|
|
37
|
+
const whereClauses = [];
|
|
38
|
+
const params = [];
|
|
39
|
+
if (parsed.entityType) {
|
|
40
|
+
whereClauses.push("entity_type = ?");
|
|
41
|
+
params.push(parsed.entityType);
|
|
42
|
+
}
|
|
43
|
+
if (parsed.entityId) {
|
|
44
|
+
whereClauses.push("entity_id = ?");
|
|
45
|
+
params.push(parsed.entityId);
|
|
46
|
+
}
|
|
47
|
+
const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
48
|
+
const limitSql = parsed.limit ? "LIMIT ?" : "";
|
|
49
|
+
if (parsed.limit) {
|
|
50
|
+
params.push(parsed.limit);
|
|
51
|
+
}
|
|
52
|
+
const rows = getDatabase()
|
|
53
|
+
.prepare(`SELECT id, entity_type, entity_id, anchor_key, body, author, source, created_at, updated_at
|
|
54
|
+
FROM entity_comments
|
|
55
|
+
${whereSql}
|
|
56
|
+
ORDER BY created_at DESC
|
|
57
|
+
${limitSql}`)
|
|
58
|
+
.all(...params);
|
|
59
|
+
return filterDeletedEntities("comment", rows.map(mapComment));
|
|
60
|
+
}
|
|
61
|
+
export function createComment(input, context) {
|
|
62
|
+
const parsed = createCommentSchema.parse(input);
|
|
63
|
+
const now = new Date().toISOString();
|
|
64
|
+
const comment = commentSchema.parse({
|
|
65
|
+
id: `cmt_${randomUUID().replaceAll("-", "").slice(0, 10)}`,
|
|
66
|
+
entityType: parsed.entityType,
|
|
67
|
+
entityId: parsed.entityId,
|
|
68
|
+
anchorKey: parsed.anchorKey,
|
|
69
|
+
body: parsed.body,
|
|
70
|
+
author: parsed.author ?? context.actor ?? null,
|
|
71
|
+
source: context.source,
|
|
72
|
+
createdAt: now,
|
|
73
|
+
updatedAt: now
|
|
74
|
+
});
|
|
75
|
+
const entityType = activityEntityTypeSchema.parse(comment.entityType);
|
|
76
|
+
getDatabase()
|
|
77
|
+
.prepare(`INSERT INTO entity_comments (id, entity_type, entity_id, anchor_key, body, author, source, created_at, updated_at)
|
|
78
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
79
|
+
.run(comment.id, comment.entityType, comment.entityId, comment.anchorKey, comment.body, comment.author, comment.source, comment.createdAt, comment.updatedAt);
|
|
80
|
+
recordActivityEvent({
|
|
81
|
+
entityType,
|
|
82
|
+
entityId: comment.entityId,
|
|
83
|
+
eventType: "comment.created",
|
|
84
|
+
title: "Comment added",
|
|
85
|
+
description: comment.body,
|
|
86
|
+
actor: comment.author ?? null,
|
|
87
|
+
source: context.source,
|
|
88
|
+
metadata: {
|
|
89
|
+
commentId: comment.id,
|
|
90
|
+
anchorKey: comment.anchorKey ?? ""
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
recordEventLog({
|
|
94
|
+
eventKind: "comment.created",
|
|
95
|
+
entityType,
|
|
96
|
+
entityId: comment.entityId,
|
|
97
|
+
actor: comment.author ?? null,
|
|
98
|
+
source: context.source,
|
|
99
|
+
metadata: {
|
|
100
|
+
commentId: comment.id,
|
|
101
|
+
anchorKey: comment.anchorKey ?? ""
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return comment;
|
|
105
|
+
}
|
|
106
|
+
export function updateComment(commentId, input, context) {
|
|
107
|
+
const existing = getCommentRow(commentId);
|
|
108
|
+
if (!existing) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const patch = updateCommentSchema.parse(input);
|
|
112
|
+
const updatedAt = new Date().toISOString();
|
|
113
|
+
const author = patch.author ?? existing.author ?? context.actor ?? null;
|
|
114
|
+
const entityType = activityEntityTypeSchema.parse(existing.entity_type);
|
|
115
|
+
getDatabase()
|
|
116
|
+
.prepare(`UPDATE entity_comments
|
|
117
|
+
SET body = ?, author = ?, updated_at = ?
|
|
118
|
+
WHERE id = ?`)
|
|
119
|
+
.run(patch.body, author, updatedAt, commentId);
|
|
120
|
+
recordActivityEvent({
|
|
121
|
+
entityType,
|
|
122
|
+
entityId: existing.entity_id,
|
|
123
|
+
eventType: "comment.updated",
|
|
124
|
+
title: "Comment updated",
|
|
125
|
+
description: patch.body,
|
|
126
|
+
actor: author,
|
|
127
|
+
source: context.source,
|
|
128
|
+
metadata: {
|
|
129
|
+
commentId
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
recordEventLog({
|
|
133
|
+
eventKind: "comment.updated",
|
|
134
|
+
entityType,
|
|
135
|
+
entityId: existing.entity_id,
|
|
136
|
+
actor: author,
|
|
137
|
+
source: context.source,
|
|
138
|
+
metadata: {
|
|
139
|
+
commentId
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return mapComment(getCommentRow(commentId));
|
|
143
|
+
}
|
|
144
|
+
export function deleteComment(commentId, context) {
|
|
145
|
+
const existing = getCommentRow(commentId);
|
|
146
|
+
if (!existing) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const entityType = activityEntityTypeSchema.parse(existing.entity_type);
|
|
150
|
+
getDatabase()
|
|
151
|
+
.prepare(`DELETE FROM entity_comments WHERE id = ?`)
|
|
152
|
+
.run(commentId);
|
|
153
|
+
recordActivityEvent({
|
|
154
|
+
entityType,
|
|
155
|
+
entityId: existing.entity_id,
|
|
156
|
+
eventType: "comment.deleted",
|
|
157
|
+
title: "Comment deleted",
|
|
158
|
+
description: existing.body,
|
|
159
|
+
actor: context.actor ?? existing.author ?? null,
|
|
160
|
+
source: context.source,
|
|
161
|
+
metadata: {
|
|
162
|
+
commentId
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
recordEventLog({
|
|
166
|
+
eventKind: "comment.deleted",
|
|
167
|
+
entityType,
|
|
168
|
+
entityId: existing.entity_id,
|
|
169
|
+
actor: context.actor ?? existing.author ?? null,
|
|
170
|
+
source: context.source,
|
|
171
|
+
metadata: {
|
|
172
|
+
commentId
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return mapComment(existing);
|
|
176
|
+
}
|