forge-openclaw-plugin 0.2.66 → 0.2.68
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/dist/assets/index-B0PIKEnz.css +1 -0
- package/dist/assets/{index-CFZxwOFB.js → index-BofyMuFh.js} +40 -40
- package/dist/index.html +2 -2
- package/dist/server/server/migrations/060_psyche_devrage_metrics.sql +40 -0
- package/dist/server/server/src/app.js +33 -5
- package/dist/server/server/src/openapi.js +76 -0
- package/dist/server/server/src/psyche-types.js +31 -0
- package/dist/server/server/src/services/devrage.js +221 -0
- package/dist/server/server/src/services/psyche.js +3 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/060_psyche_devrage_metrics.sql +40 -0
- package/dist/assets/index-lFN9z5op.css +0 -1
package/dist/index.html
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
/>
|
|
14
14
|
<link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
15
15
|
<link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
16
|
-
<script type="module" crossorigin src="/forge/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/forge/assets/index-BofyMuFh.js"></script>
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/forge/assets/vendor-BcOHGipZ.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/forge/assets/board-DFNV9VAZ.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/forge/assets/ui-g7FaEglG.js">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/forge/assets/motion-CXdn34ih.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/forge/assets/table-CEq3bTDv.js">
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/forge/assets/index-
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/forge/assets/index-B0PIKEnz.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="bg-canvas text-ink antialiased">
|
|
26
26
|
<div id="root"></div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_conversation_measures (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
source TEXT NOT NULL,
|
|
4
|
+
conversation_id TEXT NOT NULL,
|
|
5
|
+
date_key TEXT NOT NULL,
|
|
6
|
+
updated_at TEXT NOT NULL,
|
|
7
|
+
messages INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
messages_with_swears INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
swear_count INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
scanned_at TEXT NOT NULL,
|
|
11
|
+
UNIQUE (source, conversation_id, date_key)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_conversation_measures_date_idx
|
|
15
|
+
ON psyche_devrage_conversation_measures (date_key DESC);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_conversation_measures_updated_idx
|
|
18
|
+
ON psyche_devrage_conversation_measures (updated_at DESC);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_metric_measures (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
date_key TEXT NOT NULL,
|
|
23
|
+
metric_key TEXT NOT NULL,
|
|
24
|
+
value REAL NOT NULL,
|
|
25
|
+
unit TEXT NOT NULL DEFAULT '',
|
|
26
|
+
sample_count INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
computed_at TEXT NOT NULL,
|
|
28
|
+
UNIQUE (date_key, metric_key)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_metric_measures_metric_date_idx
|
|
32
|
+
ON psyche_devrage_metric_measures (metric_key, date_key DESC);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_sync_state (
|
|
35
|
+
id TEXT PRIMARY KEY CHECK (id = 'default'),
|
|
36
|
+
full_sync_completed_at TEXT,
|
|
37
|
+
last_daily_sync_at TEXT,
|
|
38
|
+
last_synced_date_key TEXT,
|
|
39
|
+
updated_at TEXT NOT NULL
|
|
40
|
+
);
|
|
@@ -46,6 +46,7 @@ import { getInsightsPayload } from "./services/insights.js";
|
|
|
46
46
|
import { buildLifeForcePayload, createFatigueSignal, listLifeForceTemplates, resolveLifeForceUser, updateLifeForceProfile, updateLifeForceTemplate } from "./services/life-force.js";
|
|
47
47
|
import { createEntities, deleteEntities, deleteEntity, getSettingsBinPayload, restoreEntities, searchEntities, updateEntities } from "./services/entity-crud.js";
|
|
48
48
|
import { getPsycheOverview } from "./services/psyche.js";
|
|
49
|
+
import { syncDevrageMetricHistoryIfNeeded } from "./services/devrage.js";
|
|
49
50
|
import { exportPsycheObservationCalendar, getPsycheObservationCalendar } from "./services/psyche-observation-calendar.js";
|
|
50
51
|
import { getProjectBoard, getProjectSummary, listProjectSummaries } from "./services/projects.js";
|
|
51
52
|
import { createDataBackup, exportData, getDataManagementState, maybeRunAutomaticBackup, restoreDataBackup, scanForDataRecoveryCandidates, switchDataRoot, updateDataManagementSettings } from "./services/data-management.js";
|
|
@@ -1957,14 +1958,15 @@ function classifyOnboardingEntity(entityType) {
|
|
|
1957
1958
|
entityType === "questionnaire_run" ||
|
|
1958
1959
|
entityType === "preference_judgment" ||
|
|
1959
1960
|
entityType === "preference_signal" ||
|
|
1960
|
-
entityType === "work_adjustment"
|
|
1961
|
+
entityType === "work_adjustment" ||
|
|
1962
|
+
entityType === "self_observation") {
|
|
1961
1963
|
return "action_workflow_entity";
|
|
1962
1964
|
}
|
|
1963
1965
|
return "read_model_only_surface";
|
|
1964
1966
|
}
|
|
1965
1967
|
function buildPreferredMutationPath(entityType) {
|
|
1966
1968
|
if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
|
|
1967
|
-
return "/api/v1/entities/create | /api/v1/entities/update | /api/v1/entities/delete | /api/v1/entities/search";
|
|
1969
|
+
return "/api/v1/entities/create | /api/v1/entities/update | /api/v1/entities/delete | /api/v1/entities/restore | /api/v1/entities/search";
|
|
1968
1970
|
}
|
|
1969
1971
|
switch (entityType) {
|
|
1970
1972
|
case "wiki_page":
|
|
@@ -1998,6 +2000,12 @@ function buildPreferredMutationPath(entityType) {
|
|
|
1998
2000
|
}
|
|
1999
2001
|
}
|
|
2000
2002
|
function buildPreferredMutationTool(entityType) {
|
|
2003
|
+
if (entityType === "sleep_session") {
|
|
2004
|
+
return "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities | forge_update_sleep_session for reflective enrichment after review";
|
|
2005
|
+
}
|
|
2006
|
+
if (entityType === "workout_session") {
|
|
2007
|
+
return "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities | forge_update_workout_session for reflective enrichment after review";
|
|
2008
|
+
}
|
|
2001
2009
|
if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
|
|
2002
2010
|
return "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities";
|
|
2003
2011
|
}
|
|
@@ -2016,6 +2024,8 @@ function buildPreferredMutationTool(entityType) {
|
|
|
2016
2024
|
return "forge_submit_preferences_signal";
|
|
2017
2025
|
case "work_adjustment":
|
|
2018
2026
|
return "forge_adjust_work_minutes";
|
|
2027
|
+
case "self_observation":
|
|
2028
|
+
return "forge_get_self_observation_calendar | forge_create_entities | forge_update_entities";
|
|
2019
2029
|
case "movement":
|
|
2020
2030
|
return "forge_call_movement_route";
|
|
2021
2031
|
case "life_force":
|
|
@@ -3417,10 +3427,11 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3417
3427
|
coachingGoal: "Help the user describe how the mode shows up, what it is trying to do, what it fears, and what burden it carries, rather than reducing it to a label only.",
|
|
3418
3428
|
askSequence: [
|
|
3419
3429
|
"Start with a recent moment when this part-state took over.",
|
|
3420
|
-
"Choose the mode family once the lived description is clearer.",
|
|
3421
|
-
"Name the mode in the user's language.",
|
|
3422
3430
|
"Describe the felt persona, body posture, imagery, or symbolic form.",
|
|
3423
|
-
"Clarify
|
|
3431
|
+
"Clarify what it is trying to protect, prevent, control, or force.",
|
|
3432
|
+
"Clarify its fear, burden, and protective job before choosing a family label.",
|
|
3433
|
+
"Offer one candidate name or formulation in the user's language and invite correction.",
|
|
3434
|
+
"Choose the mode family only after the lived description, protective job, fear, or burden is visible enough.",
|
|
3424
3435
|
"Explore when it first became necessary or familiar.",
|
|
3425
3436
|
"Notice linked patterns, behaviors, values, and what a healthy-adult response would need to do."
|
|
3426
3437
|
],
|
|
@@ -6221,10 +6232,27 @@ export async function buildServer(options = {}) {
|
|
|
6221
6232
|
void maybeRunAutomaticBackup().catch(() => {
|
|
6222
6233
|
// Ignore startup backup failures; the Data settings surface exposes recovery.
|
|
6223
6234
|
});
|
|
6235
|
+
const devrageMetricSyncEnabled = options.devrageMetricSync ?? !options.dataRoot;
|
|
6236
|
+
const devrageMetricTimer = devrageMetricSyncEnabled
|
|
6237
|
+
? setInterval(() => {
|
|
6238
|
+
void syncDevrageMetricHistoryIfNeeded().catch(() => {
|
|
6239
|
+
// Devrage is a local metric import; failures should not break Forge.
|
|
6240
|
+
});
|
|
6241
|
+
}, 60 * 60 * 1000)
|
|
6242
|
+
: null;
|
|
6243
|
+
devrageMetricTimer?.unref?.();
|
|
6244
|
+
if (devrageMetricSyncEnabled) {
|
|
6245
|
+
void syncDevrageMetricHistoryIfNeeded().catch(() => {
|
|
6246
|
+
// The Psyche metric payload exposes an empty state until sync succeeds.
|
|
6247
|
+
});
|
|
6248
|
+
}
|
|
6224
6249
|
app.addHook("onClose", async () => {
|
|
6225
6250
|
clearInterval(diagnosticRetentionTimer);
|
|
6226
6251
|
clearInterval(cronSchedulerTimer);
|
|
6227
6252
|
clearInterval(dataBackupTimer);
|
|
6253
|
+
if (devrageMetricTimer) {
|
|
6254
|
+
clearInterval(devrageMetricTimer);
|
|
6255
|
+
}
|
|
6228
6256
|
taskRunWatchdog?.stop();
|
|
6229
6257
|
await stopCompanionIroh();
|
|
6230
6258
|
await managers.backgroundJobs.stop();
|
|
@@ -4645,6 +4645,80 @@ export function buildOpenApiDocument() {
|
|
|
4645
4645
|
updatedAt: { type: "string", format: "date-time" }
|
|
4646
4646
|
}
|
|
4647
4647
|
};
|
|
4648
|
+
const devrageMetricPayload = {
|
|
4649
|
+
type: "object",
|
|
4650
|
+
additionalProperties: false,
|
|
4651
|
+
required: [
|
|
4652
|
+
"generatedAt",
|
|
4653
|
+
"latestDateKey",
|
|
4654
|
+
"rawSwearCount",
|
|
4655
|
+
"swearingMessagePercent",
|
|
4656
|
+
"conversationsScanned",
|
|
4657
|
+
"messagesScanned",
|
|
4658
|
+
"messagesWithSwears",
|
|
4659
|
+
"dailyAverage",
|
|
4660
|
+
"weeklyAverage",
|
|
4661
|
+
"history",
|
|
4662
|
+
"sync"
|
|
4663
|
+
],
|
|
4664
|
+
properties: {
|
|
4665
|
+
generatedAt: { type: "string", format: "date-time" },
|
|
4666
|
+
latestDateKey: nullable({ type: "string" }),
|
|
4667
|
+
rawSwearCount: { type: "number" },
|
|
4668
|
+
swearingMessagePercent: { type: "number" },
|
|
4669
|
+
conversationsScanned: { type: "integer" },
|
|
4670
|
+
messagesScanned: { type: "integer" },
|
|
4671
|
+
messagesWithSwears: { type: "integer" },
|
|
4672
|
+
dailyAverage: {
|
|
4673
|
+
type: "object",
|
|
4674
|
+
additionalProperties: false,
|
|
4675
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4676
|
+
properties: {
|
|
4677
|
+
rawSwearCount: { type: "number" },
|
|
4678
|
+
swearingMessagePercent: { type: "number" }
|
|
4679
|
+
}
|
|
4680
|
+
},
|
|
4681
|
+
weeklyAverage: {
|
|
4682
|
+
type: "object",
|
|
4683
|
+
additionalProperties: false,
|
|
4684
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4685
|
+
properties: {
|
|
4686
|
+
rawSwearCount: { type: "number" },
|
|
4687
|
+
swearingMessagePercent: { type: "number" }
|
|
4688
|
+
}
|
|
4689
|
+
},
|
|
4690
|
+
history: arrayOf({
|
|
4691
|
+
type: "object",
|
|
4692
|
+
additionalProperties: false,
|
|
4693
|
+
required: [
|
|
4694
|
+
"dateKey",
|
|
4695
|
+
"rawSwearCount",
|
|
4696
|
+
"swearingMessagePercent",
|
|
4697
|
+
"conversationsScanned",
|
|
4698
|
+
"messagesScanned",
|
|
4699
|
+
"messagesWithSwears"
|
|
4700
|
+
],
|
|
4701
|
+
properties: {
|
|
4702
|
+
dateKey: { type: "string" },
|
|
4703
|
+
rawSwearCount: { type: "number" },
|
|
4704
|
+
swearingMessagePercent: { type: "number" },
|
|
4705
|
+
conversationsScanned: { type: "integer" },
|
|
4706
|
+
messagesScanned: { type: "integer" },
|
|
4707
|
+
messagesWithSwears: { type: "integer" }
|
|
4708
|
+
}
|
|
4709
|
+
}),
|
|
4710
|
+
sync: {
|
|
4711
|
+
type: "object",
|
|
4712
|
+
additionalProperties: false,
|
|
4713
|
+
required: ["fullSyncCompletedAt", "lastDailySyncAt", "lastSyncedDateKey"],
|
|
4714
|
+
properties: {
|
|
4715
|
+
fullSyncCompletedAt: nullable({ type: "string", format: "date-time" }),
|
|
4716
|
+
lastDailySyncAt: nullable({ type: "string", format: "date-time" }),
|
|
4717
|
+
lastSyncedDateKey: nullable({ type: "string" })
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
};
|
|
4648
4722
|
const psycheOverviewPayload = {
|
|
4649
4723
|
type: "object",
|
|
4650
4724
|
additionalProperties: false,
|
|
@@ -4657,6 +4731,7 @@ export function buildOpenApiDocument() {
|
|
|
4657
4731
|
"beliefs",
|
|
4658
4732
|
"modes",
|
|
4659
4733
|
"schemaPressure",
|
|
4734
|
+
"devrageMetric",
|
|
4660
4735
|
"reports",
|
|
4661
4736
|
"openInsights",
|
|
4662
4737
|
"openNotes",
|
|
@@ -4680,6 +4755,7 @@ export function buildOpenApiDocument() {
|
|
|
4680
4755
|
activationCount: { type: "integer" }
|
|
4681
4756
|
}
|
|
4682
4757
|
}),
|
|
4758
|
+
devrageMetric: devrageMetricPayload,
|
|
4683
4759
|
reports: arrayOf({ $ref: "#/components/schemas/TriggerReport" }),
|
|
4684
4760
|
openInsights: { type: "integer" },
|
|
4685
4761
|
openNotes: { type: "integer" },
|
|
@@ -244,6 +244,36 @@ export const schemaPressureEntrySchema = z.object({
|
|
|
244
244
|
title: nonEmptyTrimmedString,
|
|
245
245
|
activationCount: z.number().int().nonnegative()
|
|
246
246
|
});
|
|
247
|
+
export const devrageMetricPayloadSchema = z.object({
|
|
248
|
+
generatedAt: z.string(),
|
|
249
|
+
latestDateKey: z.string().nullable(),
|
|
250
|
+
rawSwearCount: z.number().nonnegative(),
|
|
251
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
252
|
+
conversationsScanned: z.number().int().nonnegative(),
|
|
253
|
+
messagesScanned: z.number().int().nonnegative(),
|
|
254
|
+
messagesWithSwears: z.number().int().nonnegative(),
|
|
255
|
+
dailyAverage: z.object({
|
|
256
|
+
rawSwearCount: z.number().nonnegative(),
|
|
257
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
258
|
+
}),
|
|
259
|
+
weeklyAverage: z.object({
|
|
260
|
+
rawSwearCount: z.number().nonnegative(),
|
|
261
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
262
|
+
}),
|
|
263
|
+
history: z.array(z.object({
|
|
264
|
+
dateKey: z.string(),
|
|
265
|
+
rawSwearCount: z.number().nonnegative(),
|
|
266
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
267
|
+
conversationsScanned: z.number().int().nonnegative(),
|
|
268
|
+
messagesScanned: z.number().int().nonnegative(),
|
|
269
|
+
messagesWithSwears: z.number().int().nonnegative()
|
|
270
|
+
})),
|
|
271
|
+
sync: z.object({
|
|
272
|
+
fullSyncCompletedAt: z.string().nullable(),
|
|
273
|
+
lastDailySyncAt: z.string().nullable(),
|
|
274
|
+
lastSyncedDateKey: z.string().nullable()
|
|
275
|
+
})
|
|
276
|
+
});
|
|
247
277
|
export const psycheOverviewPayloadSchema = z.object({
|
|
248
278
|
generatedAt: z.string(),
|
|
249
279
|
domain: domainSchema,
|
|
@@ -254,6 +284,7 @@ export const psycheOverviewPayloadSchema = z.object({
|
|
|
254
284
|
modes: z.array(modeProfileSchema),
|
|
255
285
|
reports: z.array(triggerReportSchema),
|
|
256
286
|
schemaPressure: z.array(schemaPressureEntrySchema),
|
|
287
|
+
devrageMetric: devrageMetricPayloadSchema,
|
|
257
288
|
openInsights: z.number().int().nonnegative(),
|
|
258
289
|
openNotes: z.number().int().nonnegative(),
|
|
259
290
|
committedActions: z.array(trimmedString)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { scanConversations } from "forge-devrage";
|
|
3
|
+
import { getDatabase, runInTransaction } from "../db.js";
|
|
4
|
+
const SWEAR_COUNT_KEY = "swear_count";
|
|
5
|
+
const SWEARING_MESSAGE_PERCENT_KEY = "swearing_message_percent";
|
|
6
|
+
const DEFAULT_ROLE_FILTER = new Set(["user"]);
|
|
7
|
+
let syncInFlight = null;
|
|
8
|
+
export async function syncDevrageMetricHistory(options = {}) {
|
|
9
|
+
if (syncInFlight) {
|
|
10
|
+
return syncInFlight;
|
|
11
|
+
}
|
|
12
|
+
syncInFlight = syncDevrageMetricHistoryInternal(options).finally(() => {
|
|
13
|
+
syncInFlight = null;
|
|
14
|
+
});
|
|
15
|
+
return syncInFlight;
|
|
16
|
+
}
|
|
17
|
+
export async function syncDevrageMetricHistoryIfNeeded() {
|
|
18
|
+
const state = getDevrageSyncState();
|
|
19
|
+
if (!state?.full_sync_completed_at) {
|
|
20
|
+
await syncDevrageMetricHistory({ forceFull: true });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const today = todayDateKey();
|
|
24
|
+
if (state.last_synced_date_key !== today) {
|
|
25
|
+
await syncDevrageMetricHistory({ dateKey: today });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function getDevrageMetricPayload() {
|
|
29
|
+
const generatedAt = nowIso();
|
|
30
|
+
const state = getDevrageSyncState();
|
|
31
|
+
const history = getDevrageDailyHistory(90);
|
|
32
|
+
const latest = history[0] ?? null;
|
|
33
|
+
const dailyAverages = getMetricAverages();
|
|
34
|
+
const weeklyAverages = getMetricAverages(7);
|
|
35
|
+
return {
|
|
36
|
+
generatedAt,
|
|
37
|
+
latestDateKey: latest?.dateKey ?? null,
|
|
38
|
+
rawSwearCount: latest?.rawSwearCount ?? 0,
|
|
39
|
+
swearingMessagePercent: latest?.swearingMessagePercent ?? 0,
|
|
40
|
+
conversationsScanned: latest?.conversationsScanned ?? 0,
|
|
41
|
+
messagesScanned: latest?.messagesScanned ?? 0,
|
|
42
|
+
messagesWithSwears: latest?.messagesWithSwears ?? 0,
|
|
43
|
+
dailyAverage: {
|
|
44
|
+
rawSwearCount: dailyAverages.rawSwearCount,
|
|
45
|
+
swearingMessagePercent: dailyAverages.swearingMessagePercent
|
|
46
|
+
},
|
|
47
|
+
weeklyAverage: {
|
|
48
|
+
rawSwearCount: weeklyAverages.rawSwearCount,
|
|
49
|
+
swearingMessagePercent: weeklyAverages.swearingMessagePercent
|
|
50
|
+
},
|
|
51
|
+
history,
|
|
52
|
+
sync: {
|
|
53
|
+
fullSyncCompletedAt: state?.full_sync_completed_at ?? null,
|
|
54
|
+
lastDailySyncAt: state?.last_daily_sync_at ?? null,
|
|
55
|
+
lastSyncedDateKey: state?.last_synced_date_key ?? null
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function syncDevrageMetricHistoryInternal(options) {
|
|
60
|
+
const dateKey = options.forceFull ? undefined : options.dateKey ?? todayDateKey();
|
|
61
|
+
const report = await scanConversations({
|
|
62
|
+
roles: DEFAULT_ROLE_FILTER,
|
|
63
|
+
date: dateKey,
|
|
64
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
65
|
+
});
|
|
66
|
+
storeDevrageReport(report, {
|
|
67
|
+
fullSync: Boolean(options.forceFull),
|
|
68
|
+
syncedDateKey: dateKey ?? null
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function storeDevrageReport(report, options) {
|
|
72
|
+
const scannedAt = nowIso();
|
|
73
|
+
const affectedDateKeys = new Set(report.conversations.map((conversation) => conversation.dateKey));
|
|
74
|
+
runInTransaction(() => {
|
|
75
|
+
const database = getDatabase();
|
|
76
|
+
const deleteDate = database.prepare(`DELETE FROM psyche_devrage_conversation_measures WHERE date_key = ?`);
|
|
77
|
+
const insertConversation = database.prepare(`INSERT INTO psyche_devrage_conversation_measures (
|
|
78
|
+
id, source, conversation_id, date_key, updated_at, messages,
|
|
79
|
+
messages_with_swears, swear_count, scanned_at
|
|
80
|
+
)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
82
|
+
ON CONFLICT(source, conversation_id, date_key) DO UPDATE SET
|
|
83
|
+
updated_at = excluded.updated_at,
|
|
84
|
+
messages = excluded.messages,
|
|
85
|
+
messages_with_swears = excluded.messages_with_swears,
|
|
86
|
+
swear_count = excluded.swear_count,
|
|
87
|
+
scanned_at = excluded.scanned_at`);
|
|
88
|
+
for (const dateKey of affectedDateKeys) {
|
|
89
|
+
deleteDate.run(dateKey);
|
|
90
|
+
}
|
|
91
|
+
for (const conversation of report.conversations) {
|
|
92
|
+
insertConversation.run(stableId("devrage_conversation", conversation.source, conversation.conversationId, conversation.dateKey), conversation.source, conversation.conversationId, conversation.dateKey, conversation.updatedAt, conversation.messages, conversation.messagesWithSwears, conversation.swears, scannedAt);
|
|
93
|
+
}
|
|
94
|
+
for (const dateKey of affectedDateKeys) {
|
|
95
|
+
recomputeMetricMeasuresForDate(dateKey, scannedAt);
|
|
96
|
+
}
|
|
97
|
+
upsertSyncState({
|
|
98
|
+
fullSyncCompletedAt: options.fullSync ? scannedAt : undefined,
|
|
99
|
+
lastDailySyncAt: options.fullSync ? undefined : scannedAt,
|
|
100
|
+
lastSyncedDateKey: options.syncedDateKey ?? [...affectedDateKeys].sort().at(-1) ?? null,
|
|
101
|
+
updatedAt: scannedAt
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function recomputeMetricMeasuresForDate(dateKey, computedAt) {
|
|
106
|
+
const aggregate = getDatabase()
|
|
107
|
+
.prepare(`SELECT
|
|
108
|
+
COUNT(*) AS conversations,
|
|
109
|
+
COALESCE(SUM(messages), 0) AS messages,
|
|
110
|
+
COALESCE(SUM(messages_with_swears), 0) AS messages_with_swears,
|
|
111
|
+
COALESCE(SUM(swear_count), 0) AS swear_count
|
|
112
|
+
FROM psyche_devrage_conversation_measures
|
|
113
|
+
WHERE date_key = ?`)
|
|
114
|
+
.get(dateKey);
|
|
115
|
+
const messages = Number(aggregate.messages) || 0;
|
|
116
|
+
const messagesWithSwears = Number(aggregate.messages_with_swears) || 0;
|
|
117
|
+
const swearCount = Number(aggregate.swear_count) || 0;
|
|
118
|
+
const percent = messages > 0 ? (messagesWithSwears / messages) * 100 : 0;
|
|
119
|
+
upsertMetricMeasure(dateKey, SWEAR_COUNT_KEY, swearCount, "count", Number(aggregate.conversations) || 0, computedAt);
|
|
120
|
+
upsertMetricMeasure(dateKey, SWEARING_MESSAGE_PERCENT_KEY, percent, "percent", messages, computedAt);
|
|
121
|
+
}
|
|
122
|
+
function upsertMetricMeasure(dateKey, metricKey, value, unit, sampleCount, computedAt) {
|
|
123
|
+
getDatabase()
|
|
124
|
+
.prepare(`INSERT INTO psyche_devrage_metric_measures (
|
|
125
|
+
id, date_key, metric_key, value, unit, sample_count, computed_at
|
|
126
|
+
)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
128
|
+
ON CONFLICT(date_key, metric_key) DO UPDATE SET
|
|
129
|
+
value = excluded.value,
|
|
130
|
+
unit = excluded.unit,
|
|
131
|
+
sample_count = excluded.sample_count,
|
|
132
|
+
computed_at = excluded.computed_at`)
|
|
133
|
+
.run(stableId("devrage_metric", dateKey, metricKey), dateKey, metricKey, value, unit, sampleCount, computedAt);
|
|
134
|
+
}
|
|
135
|
+
function upsertSyncState(input) {
|
|
136
|
+
const current = getDevrageSyncState();
|
|
137
|
+
getDatabase()
|
|
138
|
+
.prepare(`INSERT INTO psyche_devrage_sync_state (
|
|
139
|
+
id, full_sync_completed_at, last_daily_sync_at, last_synced_date_key, updated_at
|
|
140
|
+
)
|
|
141
|
+
VALUES ('default', ?, ?, ?, ?)
|
|
142
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
143
|
+
full_sync_completed_at = excluded.full_sync_completed_at,
|
|
144
|
+
last_daily_sync_at = excluded.last_daily_sync_at,
|
|
145
|
+
last_synced_date_key = excluded.last_synced_date_key,
|
|
146
|
+
updated_at = excluded.updated_at`)
|
|
147
|
+
.run(input.fullSyncCompletedAt ?? current?.full_sync_completed_at ?? null, input.lastDailySyncAt ?? current?.last_daily_sync_at ?? null, input.lastSyncedDateKey ?? current?.last_synced_date_key ?? null, input.updatedAt);
|
|
148
|
+
}
|
|
149
|
+
function getDevrageSyncState() {
|
|
150
|
+
return (getDatabase()
|
|
151
|
+
.prepare(`SELECT full_sync_completed_at, last_daily_sync_at, last_synced_date_key, updated_at
|
|
152
|
+
FROM psyche_devrage_sync_state
|
|
153
|
+
WHERE id = 'default'`)
|
|
154
|
+
.get() ?? null);
|
|
155
|
+
}
|
|
156
|
+
function getDevrageDailyHistory(limit) {
|
|
157
|
+
const rows = getDatabase()
|
|
158
|
+
.prepare(`SELECT
|
|
159
|
+
date_key,
|
|
160
|
+
COUNT(*) AS conversations,
|
|
161
|
+
COALESCE(SUM(messages), 0) AS messages,
|
|
162
|
+
COALESCE(SUM(messages_with_swears), 0) AS messages_with_swears,
|
|
163
|
+
COALESCE(SUM(swear_count), 0) AS swear_count
|
|
164
|
+
FROM psyche_devrage_conversation_measures
|
|
165
|
+
GROUP BY date_key
|
|
166
|
+
ORDER BY date_key DESC
|
|
167
|
+
LIMIT ?`)
|
|
168
|
+
.all(limit);
|
|
169
|
+
return rows.map((row) => {
|
|
170
|
+
const messages = Number(row.messages) || 0;
|
|
171
|
+
const messagesWithSwears = Number(row.messages_with_swears) || 0;
|
|
172
|
+
return {
|
|
173
|
+
dateKey: row.date_key,
|
|
174
|
+
rawSwearCount: Number(row.swear_count) || 0,
|
|
175
|
+
swearingMessagePercent: messages > 0 ? (messagesWithSwears / messages) * 100 : 0,
|
|
176
|
+
conversationsScanned: Number(row.conversations) || 0,
|
|
177
|
+
messagesScanned: messages,
|
|
178
|
+
messagesWithSwears
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function getMetricAverages(days) {
|
|
183
|
+
const where = days
|
|
184
|
+
? `WHERE date_key IN (
|
|
185
|
+
SELECT date_key FROM psyche_devrage_metric_measures
|
|
186
|
+
GROUP BY date_key
|
|
187
|
+
ORDER BY date_key DESC
|
|
188
|
+
LIMIT ?
|
|
189
|
+
)`
|
|
190
|
+
: "";
|
|
191
|
+
const rows = getDatabase()
|
|
192
|
+
.prepare(`SELECT metric_key, AVG(value) AS value
|
|
193
|
+
FROM psyche_devrage_metric_measures
|
|
194
|
+
${where}
|
|
195
|
+
GROUP BY metric_key`)
|
|
196
|
+
.all(...(days ? [days] : []));
|
|
197
|
+
const swearAverage = rows.find((row) => row.metric_key === SWEAR_COUNT_KEY)?.value ?? 0;
|
|
198
|
+
const percentAverage = rows.find((row) => row.metric_key === SWEARING_MESSAGE_PERCENT_KEY)?.value ?? 0;
|
|
199
|
+
return {
|
|
200
|
+
rawSwearCount: round(Number(swearAverage) || 0, 1),
|
|
201
|
+
swearingMessagePercent: round(Number(percentAverage) || 0, 1)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function stableId(prefix, ...parts) {
|
|
205
|
+
const digest = createHash("sha256").update(parts.join("\u0000")).digest("hex").slice(0, 20);
|
|
206
|
+
return `${prefix}_${digest}`;
|
|
207
|
+
}
|
|
208
|
+
function todayDateKey() {
|
|
209
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
210
|
+
year: "numeric",
|
|
211
|
+
month: "2-digit",
|
|
212
|
+
day: "2-digit"
|
|
213
|
+
}).format(new Date());
|
|
214
|
+
}
|
|
215
|
+
function nowIso() {
|
|
216
|
+
return new Date().toISOString();
|
|
217
|
+
}
|
|
218
|
+
function round(value, digits) {
|
|
219
|
+
const factor = 10 ** digits;
|
|
220
|
+
return Math.round(value * factor) / factor;
|
|
221
|
+
}
|
|
@@ -3,6 +3,7 @@ import { listInsights } from "../repositories/collaboration.js";
|
|
|
3
3
|
import { listNotes } from "../repositories/notes.js";
|
|
4
4
|
import { filterOwnedEntities } from "../repositories/entity-ownership.js";
|
|
5
5
|
import { listBehaviorPatterns, listBehaviors, listBeliefEntries, listModeProfiles, listPsycheValues, listSchemaCatalog, listTriggerReports } from "../repositories/psyche.js";
|
|
6
|
+
import { getDevrageMetricPayload } from "./devrage.js";
|
|
6
7
|
import { psycheOverviewPayloadSchema } from "../psyche-types.js";
|
|
7
8
|
const PSYCHE_ENTITY_TYPE_SET = new Set([
|
|
8
9
|
"psyche_value",
|
|
@@ -32,6 +33,7 @@ export function getPsycheOverview(userIds) {
|
|
|
32
33
|
...behaviors.filter((behavior) => behavior.kind === "committed").map((behavior) => behavior.title),
|
|
33
34
|
...reports.flatMap((report) => report.nextMoves)
|
|
34
35
|
];
|
|
36
|
+
const devrageMetric = getDevrageMetricPayload();
|
|
35
37
|
const schemaPressure = schemaCatalog
|
|
36
38
|
.filter((schema) => schema.schemaType === "maladaptive")
|
|
37
39
|
.map((schema) => {
|
|
@@ -57,6 +59,7 @@ export function getPsycheOverview(userIds) {
|
|
|
57
59
|
modes,
|
|
58
60
|
reports,
|
|
59
61
|
schemaPressure,
|
|
62
|
+
devrageMetric,
|
|
60
63
|
openInsights,
|
|
61
64
|
openNotes,
|
|
62
65
|
committedActions
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "forge-openclaw-plugin",
|
|
3
3
|
"name": "Forge",
|
|
4
4
|
"description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.68",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_conversation_measures (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
source TEXT NOT NULL,
|
|
4
|
+
conversation_id TEXT NOT NULL,
|
|
5
|
+
date_key TEXT NOT NULL,
|
|
6
|
+
updated_at TEXT NOT NULL,
|
|
7
|
+
messages INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
messages_with_swears INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
swear_count INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
scanned_at TEXT NOT NULL,
|
|
11
|
+
UNIQUE (source, conversation_id, date_key)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_conversation_measures_date_idx
|
|
15
|
+
ON psyche_devrage_conversation_measures (date_key DESC);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_conversation_measures_updated_idx
|
|
18
|
+
ON psyche_devrage_conversation_measures (updated_at DESC);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_metric_measures (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
date_key TEXT NOT NULL,
|
|
23
|
+
metric_key TEXT NOT NULL,
|
|
24
|
+
value REAL NOT NULL,
|
|
25
|
+
unit TEXT NOT NULL DEFAULT '',
|
|
26
|
+
sample_count INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
computed_at TEXT NOT NULL,
|
|
28
|
+
UNIQUE (date_key, metric_key)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS psyche_devrage_metric_measures_metric_date_idx
|
|
32
|
+
ON psyche_devrage_metric_measures (metric_key, date_key DESC);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS psyche_devrage_sync_state (
|
|
35
|
+
id TEXT PRIMARY KEY CHECK (id = 'default'),
|
|
36
|
+
full_sync_completed_at TEXT,
|
|
37
|
+
last_daily_sync_at TEXT,
|
|
38
|
+
last_synced_date_key TEXT,
|
|
39
|
+
updated_at TEXT NOT NULL
|
|
40
|
+
);
|