forge-openclaw-plugin 0.3.0 → 0.3.1
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/{activity-page-DPPiiyLO.js → activity-page-DRRbpVK-.js} +1 -1
- package/dist/assets/{ai-surface-workspace-B_fV9C4s.js → ai-surface-workspace-DJPt-OaW.js} +1 -1
- package/dist/assets/{atlas-panel-BPTJyMcI.js → atlas-panel-G3wGxjYf.js} +1 -1
- package/dist/assets/{calendar-page-D4DcRQv7.js → calendar-page-y8G1ige8.js} +1 -1
- package/dist/assets/{calendar-rules-Cl_xMrBn.js → calendar-rules-CRhXqGQu.js} +1 -1
- package/dist/assets/{calendar-week-toolbar-BARSOEO9.js → calendar-week-toolbar-CWSK5hJl.js} +1 -1
- package/dist/assets/{companion-sync-lab-page-COFYDI3w.js → companion-sync-lab-page-kEgOF3c6.js} +1 -1
- package/dist/assets/daily-metrics-dashboard-BJjUIZVA.js +1 -0
- package/dist/assets/{entity-note-count-link-BX93WYgh.js → entity-note-count-link-ef3ZOpKM.js} +1 -1
- package/dist/assets/{entity-notes-surface-CBlKc-x-.js → entity-notes-surface-CaCjjUaq.js} +1 -1
- package/dist/assets/{execution-board-Dh41UeKR.js → execution-board-RRWm2srb.js} +1 -1
- package/dist/assets/{faceted-token-search-BgxYH_gW.js → faceted-token-search-CZmtyJTT.js} +1 -1
- package/dist/assets/{flagship-signal-deck-bolzzK8q.js → flagship-signal-deck-DxRfmfpN.js} +1 -1
- package/dist/assets/{floating-action-menu-C5H-Wr2B.js → floating-action-menu-cKjNsPYD.js} +1 -1
- package/dist/assets/{goal-detail-page-CQeFnGDJ.js → goal-detail-page-BgQlK7yT.js} +1 -1
- package/dist/assets/{goals-page-BTg9crEK.js → goals-page-Cz2i3pVy.js} +1 -1
- package/dist/assets/{habits-page-C4vqk346.js → habits-page-BmEmWCGv.js} +1 -1
- package/dist/assets/index-Bcem7l5u.css +1 -0
- package/dist/assets/{index-By3tQxiE.js → index-QJkjKYzZ.js} +2 -2
- package/dist/assets/{insight-flow-dialog-JPYCsTKI.js → insight-flow-dialog-CdYMHwMP.js} +1 -1
- package/dist/assets/{insights-page-CFhVMlVL.js → insights-page-DHG-o9P_.js} +1 -1
- package/dist/assets/{kanban-page-DC3IDsnh.js → kanban-page-C6S_fJRc.js} +1 -1
- package/dist/assets/{knowledge-graph-page-DucyRYTZ.js → knowledge-graph-page-juA9QCy0.js} +1 -1
- package/dist/assets/{life-force-page-UrXfxaSO.js → life-force-page-ChTIA4mV.js} +1 -1
- package/dist/assets/{life-force-workspace-BhPNH7ad.js → life-force-workspace-sszLD-Yn.js} +1 -1
- package/dist/assets/{metric-tile-DtRPjN4V.js → metric-tile-B-tDTcBt.js} +1 -1
- package/dist/assets/{movement-page-DCIdTQEN.js → movement-page-Dx3T3HrG.js} +1 -1
- package/dist/assets/{note-markdown-CepDxj3v.js → note-markdown-CCyyE85h.js} +1 -1
- package/dist/assets/{note-tags-input-BXYaDkOr.js → note-tags-input-BS14YXHn.js} +1 -1
- package/dist/assets/{notes-page-Bl1LlCLo.js → notes-page-D3yGl8TN.js} +1 -1
- package/dist/assets/{open-in-graph-button-C263zl5l.js → open-in-graph-button-CMjJZX7G.js} +1 -1
- package/dist/assets/{orbit-map-Cb_AJyIj.js → orbit-map-v2Pv9RU_.js} +1 -1
- package/dist/assets/{overview-page-C2yLity7.js → overview-page-DSOpBpHf.js} +1 -1
- package/dist/assets/{page-hero-5RTqAI88.js → page-hero-CONuJu5J.js} +1 -1
- package/dist/assets/{pill-cluster-CBrrtZBm.js → pill-cluster-W2fY48OM.js} +1 -1
- package/dist/assets/{preference-entity-handoff-button-BQzg2J4k.js → preference-entity-handoff-button-CKDxCnbl.js} +1 -1
- package/dist/assets/{preferences-page-DekYF-Yz.js → preferences-page-Br3zrLlH.js} +1 -1
- package/dist/assets/{project-collections-BVXB8PPF.js → project-collections-NBwk6yV-.js} +1 -1
- package/dist/assets/{project-detail-page-CQlrLEn9.js → project-detail-page-BIU9_TRN.js} +1 -1
- package/dist/assets/{project-management-hierarchy-page--mzM_zRE.js → project-management-hierarchy-page-C2H620xD.js} +1 -1
- package/dist/assets/{project-management-section-nav-DQl0qRWy.js → project-management-section-nav-DRPVS1Eh.js} +1 -1
- package/dist/assets/{projects-page-Dr_0NdVP.js → projects-page-w7a_L2-z.js} +1 -1
- package/dist/assets/{psyche-behaviors-page-DOUaCWON.js → psyche-behaviors-page-Dt4CMC-q.js} +1 -1
- package/dist/assets/{psyche-flashcards-page-BA84fO_X.js → psyche-flashcards-page-rXz06dqO.js} +1 -1
- package/dist/assets/{psyche-goal-map-page-DpXAg1Vf.js → psyche-goal-map-page-BCcD6Jkl.js} +1 -1
- package/dist/assets/{psyche-graph-BxcbAbXt.js → psyche-graph-CXlAj4ED.js} +1 -1
- package/dist/assets/psyche-metrics-page-B1otiir6.js +1 -0
- package/dist/assets/{psyche-mode-guide-page-CBlIS5N_.js → psyche-mode-guide-page-C6AdVIAv.js} +1 -1
- package/dist/assets/{psyche-modes-page-DE6KDK_1.js → psyche-modes-page-DoUqz2mR.js} +1 -1
- package/dist/assets/psyche-page-BJNLQn8W.js +1 -0
- package/dist/assets/{psyche-patterns-page-DbCNRuww.js → psyche-patterns-page-CzpMSAPu.js} +1 -1
- package/dist/assets/{psyche-questionnaire-builder-page-DnraDj1H.js → psyche-questionnaire-builder-page-DjzatGVZ.js} +1 -1
- package/dist/assets/{psyche-questionnaire-detail-page-DQ6zNQkk.js → psyche-questionnaire-detail-page-B0Qfowfg.js} +1 -1
- package/dist/assets/{psyche-questionnaire-run-detail-page-BGrOIX0B.js → psyche-questionnaire-run-detail-page-B2dNj4jJ.js} +1 -1
- package/dist/assets/{psyche-questionnaire-run-page-Bg3BhBHb.js → psyche-questionnaire-run-page-DyD9Ui-g.js} +1 -1
- package/dist/assets/{psyche-questionnaires-page-BEreTNjG.js → psyche-questionnaires-page-BxfKcoi3.js} +1 -1
- package/dist/assets/{psyche-report-detail-page-Br9KlJBG.js → psyche-report-detail-page-Ci14mNUK.js} +1 -1
- package/dist/assets/{psyche-reports-page-Bk6QpAEF.js → psyche-reports-page-ALhNueiV.js} +1 -1
- package/dist/assets/{psyche-schemas-beliefs-page-Db8d2ga7.js → psyche-schemas-beliefs-page-C0P2Vw6-.js} +1 -1
- package/dist/assets/{psyche-screen-time-page-NazHUAkT.js → psyche-screen-time-page-BYnaT3cb.js} +1 -1
- package/dist/assets/{psyche-self-observation-page-H6FToucM.js → psyche-self-observation-page-eM9vpwIB.js} +1 -1
- package/dist/assets/{psyche-values-page-DqM382-6.js → psyche-values-page-BqrGIDok.js} +1 -1
- package/dist/assets/{report-chain-fields-nW4zndrM.js → report-chain-fields-CbkT7WOO.js} +1 -1
- package/dist/assets/{rewards-page-C-tZVnIK.js → rewards-page-DpAJ8IxE.js} +1 -1
- package/dist/assets/{scheduling-rules-editor-DGF-044c.js → scheduling-rules-editor-BUGnZJ0g.js} +1 -1
- package/dist/assets/{schema-badge-57AJ3FY-.js → schema-badge-CLAA4p-b.js} +1 -1
- package/dist/assets/{select-menu-mfaklv7r.js → select-menu-Cqs5t3ng.js} +1 -1
- package/dist/assets/{settings-agents-page-CPJeeYg0.js → settings-agents-page-pmkVrOO8.js} +1 -1
- package/dist/assets/{settings-bin-page-D3eX5aNf.js → settings-bin-page-BwtLrweW.js} +1 -1
- package/dist/assets/{settings-calendar-page-Bgx8NIsP.js → settings-calendar-page-BBjAMOCf.js} +1 -1
- package/dist/assets/{settings-data-page-BuMSUjpp.js → settings-data-page-Bqy5SukM.js} +1 -1
- package/dist/assets/{settings-logs-page-CHb2GNUM.js → settings-logs-page-8khtrf4T.js} +1 -1
- package/dist/assets/{settings-mobile-page-ClUvXhJg.js → settings-mobile-page-BcyRjEU_.js} +1 -1
- package/dist/assets/{settings-models-page-njK6D7zc.js → settings-models-page-DlSnO9WX.js} +1 -1
- package/dist/assets/{settings-page-Dm--cx6B.js → settings-page-CyBQ2qcB.js} +1 -1
- package/dist/assets/{settings-rewards-page-3aShLM3A.js → settings-rewards-page-BBX2WtVM.js} +1 -1
- package/dist/assets/{settings-section-nav-BWox23Qv.js → settings-section-nav-Co0AaDG-.js} +1 -1
- package/dist/assets/{settings-users-page-D-hEPpgQ.js → settings-users-page-JSnfFFPu.js} +1 -1
- package/dist/assets/{settings-wiki-page-CC-rOtDm.js → settings-wiki-page--nT23Fdy.js} +1 -1
- package/dist/assets/{sleep-page-Cm_Ei2w1.js → sleep-page-CGMi3MtV.js} +1 -1
- package/dist/assets/{sports-page-CqSI-j-H.js → sports-page-Kv5bBoLr.js} +1 -1
- package/dist/assets/{strategies-page-BuJ4zLLv.js → strategies-page-Br7RdUJO.js} +1 -1
- package/dist/assets/{strategy-detail-page-CLmy0jbW.js → strategy-detail-page-CBWXjUhC.js} +1 -1
- package/dist/assets/{strategy-dialog-DnJix1ys.js → strategy-dialog-BTwkaSxI.js} +1 -1
- package/dist/assets/{surface-DRNu5Fpz.js → surface-CyWI7e1R.js} +1 -1
- package/dist/assets/{task-detail-page-DTnNjW33.js → task-detail-page-Qis9354n.js} +1 -1
- package/dist/assets/{timebox-planning-dialog-DckIr1K4.js → timebox-planning-dialog-DB8snOBD.js} +1 -1
- package/dist/assets/{today-page-Cgk4HBPK.js → today-page-D9A5PU0S.js} +1 -1
- package/dist/assets/{training-load-page-C-7iINaB.js → training-load-page-Bpd4DQAh.js} +1 -1
- package/dist/assets/vitals-page-CVYC-4RG.js +1 -0
- package/dist/assets/{weekly-review-page-DFmuzzig.js → weekly-review-page-BP7N3fNB.js} +1 -1
- package/dist/assets/{weight-loss-page-CWtgeuti.js → weight-loss-page-_fX3N1UK.js} +1 -1
- package/dist/assets/{wiki-article-markdown-BkQg8Hne.js → wiki-article-markdown-Bi02AsVt.js} +1 -1
- package/dist/assets/{wiki-editor-page-BYKcdX5I.js → wiki-editor-page-OZf9l4Tg.js} +1 -1
- package/dist/assets/{wiki-ingest-history-page-D_p4myw1.js → wiki-ingest-history-page-CzeNSL7N.js} +1 -1
- package/dist/assets/{wiki-ingest-modal-C-CidueO.js → wiki-ingest-modal-Ig_xnyc6.js} +1 -1
- package/dist/assets/wiki-page-B4xuNgY4.js +1 -0
- package/dist/assets/{workbench-flow-page-Q3r2IJty.js → workbench-flow-page-BhRJCALC.js} +1 -1
- package/dist/assets/{workbench-page-BFfWMGm6.js → workbench-page-DxVs2AOY.js} +1 -1
- package/dist/assets/{workout-detail-page-DeDyz1Kk.js → workout-detail-page-D4wfkE98.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/server/server/migrations/069_psyche_devrage_cumulative_rage.sql +5 -0
- package/dist/server/server/src/app.js +3 -2
- package/dist/server/server/src/openapi.js +48 -8
- package/dist/server/server/src/psyche-types.js +18 -4
- package/dist/server/server/src/services/devrage-scanner.js +115 -10
- package/dist/server/server/src/services/devrage.js +61 -11
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/069_psyche_devrage_cumulative_rage.sql +5 -0
- package/skills/forge-openclaw/SKILL.md +7 -0
- package/skills/forge-openclaw/entity_conversation_playbooks.md +23 -0
- package/skills/forge-openclaw/psyche_entity_playbooks.md +32 -0
- package/dist/assets/daily-metrics-dashboard-BNGQlJkQ.js +0 -1
- package/dist/assets/index-BAXYM89v.css +0 -1
- package/dist/assets/psyche-metrics-page-D8JQEoOl.js +0 -1
- package/dist/assets/psyche-page-Dkh7R2Dc.js +0 -1
- package/dist/assets/vitals-page-CqE4bYMf.js +0 -1
- package/dist/assets/wiki-page-DYpg9ZEO.js +0 -1
|
@@ -3114,6 +3114,7 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
|
|
|
3114
3114
|
"For direct update or review requests, the next question should usually narrow the saved object, timeframe, or route family instead of reopening the whole meaning-making arc.",
|
|
3115
3115
|
"For updates, start with the smallest thing that now feels wrong, newly true, or newly visible rather than restarting the whole story.",
|
|
3116
3116
|
"For review requests, ask what practical question the user wants the read to answer before you ask for more scope.",
|
|
3117
|
+
"For review-first requests, use the correct read posture before asking write-shaped questions: shared batch search or read hints for normal entities, wiki/calendar dedicated reads for specialized CRUD, read-model routes for overviews, and Movement, Life Force, or Workbench dedicated reads for those domain surfaces. After the read, answer the practical question before asking for any save, correction, link, run, enrichment, or publish detail.",
|
|
3117
3118
|
"After a review, overview, navigation, or specialized read returns data, first answer the user's practical question in plain language, then name one implication or uncertainty that matters for the next decision. Ask a follow-up only if it changes the next action: save, update, correct, link, schedule, run, publish, enrich, or open the UI.",
|
|
3118
3119
|
"Treat userId, owner, and human/bot assignees as accountability and scope, not as opening form fields. Ask whose record or owner scope matters only when it changes visibility, review results, collaboration, automation behavior, or later filtering.",
|
|
3119
3120
|
"For read and overview requests, ask for human or bot user scope only when the answer would meaningfully differ across owners; otherwise keep the next question focused on the user's practical question.",
|
|
@@ -5455,10 +5456,10 @@ function buildAgentOnboardingPayload(request) {
|
|
|
5455
5456
|
maxQuestionsPerTurn: 1,
|
|
5456
5457
|
psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. After one concrete example is clear and a hypothesis lands or is corrected, translate it into a saveable record shape such as a belief sentence, functional loop, behavior, mode, trigger report, value, event type, or emotion definition; ask one accuracy question instead of reopening broad exploration, then use the shared batch entity routes after the user accepts the wording or explicitly asks to save. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation, begin with the smallest part of the old wording that no longer fits, and do not reopen the full origin story unless the new understanding is truly structural. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
|
|
5457
5458
|
specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the job first, then choose the dedicated route family internally and do not guess at a generic CRUD path. Use specializedDomainSurfaces.routeSelectionQuestions when they are present so the next follow-up selects the right route instead of asking generic questions. When available, use forge_call_movement_route, forge_call_life_force_route, or forge_call_workbench_route after the lane is clear. In user-facing language, talk about timeline, overlay, weekday template, published output, run detail, or node result rather than surfaces, payloads, read paths, mutation paths, or CRUD. If the truth of the current state is still uncertain, read the relevant dedicated view before you mutate it. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. After a concrete Movement, Life Force, or Workbench correction, read the relevant view back when the user is trying to understand the result rather than just store it. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
|
|
5458
|
-
reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
|
|
5459
|
+
reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Use the correct read posture before asking write-shaped questions: shared batch search or read hints for normal entities, wiki/calendar dedicated reads for specialized CRUD, read-model routes for overviews, and Movement, Life Force, or Workbench dedicated reads for those domain surfaces. After the read, answer the practical question before asking for any save, correction, link, run, enrichment, or publish detail. Do not reopen the whole intake unless the user is actually redefining the record.",
|
|
5459
5460
|
readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt only when a lightweight episode observation is the right container. Do not use it as the default bucket for Psyche material: prefer trigger_report for one emotionally meaningful episode, behavior_pattern for functional analysis of a recurring loop, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for a central part-state, and wiki_page for durable memory such as books, articles, concepts, sources, or personal manuals. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
|
|
5460
5461
|
psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
|
|
5461
|
-
psycheHypothesisRule: "When one concrete Psyche example is visible, a helpful hypothesis should start from evidence in the user's own example, offer one testable interpretation, name the function without blame such as protection, prediction, relief, or cost, and ask whether the danger, need, or wording fits. Do not present schema, mode, belief, or pattern language as a verdict. If the user corrects the hypothesis, revise it once and move toward the saveable record shape instead of asking for another broad story.",
|
|
5462
|
+
psycheHypothesisRule: "When one concrete Psyche example is visible, a helpful hypothesis should start from evidence in the user's own example, offer one testable interpretation, name the function without blame such as protection, prediction, relief, or cost, and ask whether the danger, need, or wording fits. Use the hypothesis timing checkpoint before asking a second or third deepening question: offer a hypothesis when one concrete episode, body cue, belief sentence, behavior, or mode voice is visible and the hypothesis would change the record shape, wording, links, or next action. Do not hypothesize yet when no concrete moment is visible, the user only wants a direct mechanical save, the user is flooded or unsafe, or the only available interpretation would be diagnosis-like, an origin story, or a certainty claim. Do not present schema, mode, belief, or pattern language as a verdict. If the user corrects the hypothesis, revise it once and move toward the saveable record shape instead of asking for another broad story.",
|
|
5462
5463
|
mixedIntentSequencingRule: "When one user message combines several Forge jobs, identify the primary job and the order of operations before asking a follow-up. If a read changes the truth of a later write, read first: Movement timeline or box detail before correction, Workbench run or node detail before editing or publishing, and Life Force overview before changing durable assumptions when the current energy picture is uncertain. If the user asks to understand and save Psyche material plus create a support record, formulate the primary Psyche record first, then derive the flashcard, note, link, task, or habit from the accepted wording. If the user already gave the concrete action, do not ask a broad lane question; say the product sequence briefly and ask only for the missing span, wording, flow, run, node, weekday, or link that changes the next action.",
|
|
5463
5464
|
duplicateDisambiguationRule: "Before creating or updating a normal stored entity when duplicate risk is plausible, search the shared batch entity route by entity type, distinctive title or wording, owner scope, and linked content. If a likely existing record appears, ask whether the user wants to update that record, link to it, or save a separate new record; do not reopen the whole create flow. For Psyche records, a similar belief, pattern, mode, trigger report, value, or flashcard is a formulation choice, not a duplicate error: compare the sentence, cue/payoff/cost, protective job, episode, urge sentence, or message and let the user choose update, link, or new version. For wiki_page and calendar_connection, use dedicated search/list/read routes before creating another page or connection. For Movement, Life Force, and Workbench, use the dedicated read lanes instead of batch duplicate search.",
|
|
5464
5465
|
destructiveActionRule: "Before deleting, archiving, invalidating, overwriting, disconnecting, or substantially replacing a Forge record or specialized object, confirm the exact target and what should remain understandable. Prefer normal soft-delete for stored entities unless the user explicitly asks for permanent removal. For Psyche records, preserve therapeutic history by asking whether the old belief, pattern, mode, trigger report, value, or flashcard should be updated, linked as history, archived, or kept distinct; do not delete it just because a cleaner formulation exists. For Movement, distinguish user-defined overlay deletion from automatic-box invalidation and stay/trip/point deletion, and read the specific span first when the target is uncertain. For calendar connections, Workbench flows, wiki pages, and questionnaire instruments, ask what downstream sync, published output, backlinks, run history, or completed runs should remain understandable before deleting or replacing the saved object.",
|
|
@@ -4766,6 +4766,9 @@ export function buildOpenApiDocument() {
|
|
|
4766
4766
|
"latestDateKey",
|
|
4767
4767
|
"rawSwearCount",
|
|
4768
4768
|
"swearingMessagePercent",
|
|
4769
|
+
"averageMaxCumulativeRage",
|
|
4770
|
+
"maxCumulativeRage",
|
|
4771
|
+
"maxSwearingStreak",
|
|
4769
4772
|
"conversationsScanned",
|
|
4770
4773
|
"messagesScanned",
|
|
4771
4774
|
"messagesWithSwears",
|
|
@@ -4780,25 +4783,42 @@ export function buildOpenApiDocument() {
|
|
|
4780
4783
|
latestDateKey: nullable({ type: "string" }),
|
|
4781
4784
|
rawSwearCount: { type: "number" },
|
|
4782
4785
|
swearingMessagePercent: { type: "number" },
|
|
4786
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
4787
|
+
maxCumulativeRage: { type: "number" },
|
|
4788
|
+
maxSwearingStreak: { type: "integer" },
|
|
4783
4789
|
conversationsScanned: { type: "integer" },
|
|
4784
4790
|
messagesScanned: { type: "integer" },
|
|
4785
4791
|
messagesWithSwears: { type: "integer" },
|
|
4786
4792
|
dailyAverage: {
|
|
4787
4793
|
type: "object",
|
|
4788
4794
|
additionalProperties: false,
|
|
4789
|
-
required: [
|
|
4795
|
+
required: [
|
|
4796
|
+
"rawSwearCount",
|
|
4797
|
+
"swearingMessagePercent",
|
|
4798
|
+
"averageMaxCumulativeRage",
|
|
4799
|
+
"maxCumulativeRage"
|
|
4800
|
+
],
|
|
4790
4801
|
properties: {
|
|
4791
4802
|
rawSwearCount: { type: "number" },
|
|
4792
|
-
swearingMessagePercent: { type: "number" }
|
|
4803
|
+
swearingMessagePercent: { type: "number" },
|
|
4804
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
4805
|
+
maxCumulativeRage: { type: "number" }
|
|
4793
4806
|
}
|
|
4794
4807
|
},
|
|
4795
4808
|
weeklyAverage: {
|
|
4796
4809
|
type: "object",
|
|
4797
4810
|
additionalProperties: false,
|
|
4798
|
-
required: [
|
|
4811
|
+
required: [
|
|
4812
|
+
"rawSwearCount",
|
|
4813
|
+
"swearingMessagePercent",
|
|
4814
|
+
"averageMaxCumulativeRage",
|
|
4815
|
+
"maxCumulativeRage"
|
|
4816
|
+
],
|
|
4799
4817
|
properties: {
|
|
4800
4818
|
rawSwearCount: { type: "number" },
|
|
4801
|
-
swearingMessagePercent: { type: "number" }
|
|
4819
|
+
swearingMessagePercent: { type: "number" },
|
|
4820
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
4821
|
+
maxCumulativeRage: { type: "number" }
|
|
4802
4822
|
}
|
|
4803
4823
|
},
|
|
4804
4824
|
history: arrayOf({
|
|
@@ -4808,6 +4828,9 @@ export function buildOpenApiDocument() {
|
|
|
4808
4828
|
"dateKey",
|
|
4809
4829
|
"rawSwearCount",
|
|
4810
4830
|
"swearingMessagePercent",
|
|
4831
|
+
"averageMaxCumulativeRage",
|
|
4832
|
+
"maxCumulativeRage",
|
|
4833
|
+
"maxSwearingStreak",
|
|
4811
4834
|
"conversationsScanned",
|
|
4812
4835
|
"messagesScanned",
|
|
4813
4836
|
"messagesWithSwears"
|
|
@@ -4816,6 +4839,9 @@ export function buildOpenApiDocument() {
|
|
|
4816
4839
|
dateKey: { type: "string" },
|
|
4817
4840
|
rawSwearCount: { type: "number" },
|
|
4818
4841
|
swearingMessagePercent: { type: "number" },
|
|
4842
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
4843
|
+
maxCumulativeRage: { type: "number" },
|
|
4844
|
+
maxSwearingStreak: { type: "integer" },
|
|
4819
4845
|
conversationsScanned: { type: "integer" },
|
|
4820
4846
|
messagesScanned: { type: "integer" },
|
|
4821
4847
|
messagesWithSwears: { type: "integer" }
|
|
@@ -4952,19 +4978,33 @@ export function buildOpenApiDocument() {
|
|
|
4952
4978
|
dailyAverage: {
|
|
4953
4979
|
type: "object",
|
|
4954
4980
|
additionalProperties: false,
|
|
4955
|
-
required: [
|
|
4981
|
+
required: [
|
|
4982
|
+
"rawSwearCount",
|
|
4983
|
+
"swearingMessagePercent",
|
|
4984
|
+
"averageMaxCumulativeRage",
|
|
4985
|
+
"maxCumulativeRage"
|
|
4986
|
+
],
|
|
4956
4987
|
properties: {
|
|
4957
4988
|
rawSwearCount: { type: "number" },
|
|
4958
|
-
swearingMessagePercent: { type: "number" }
|
|
4989
|
+
swearingMessagePercent: { type: "number" },
|
|
4990
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
4991
|
+
maxCumulativeRage: { type: "number" }
|
|
4959
4992
|
}
|
|
4960
4993
|
},
|
|
4961
4994
|
weeklyAverage: {
|
|
4962
4995
|
type: "object",
|
|
4963
4996
|
additionalProperties: false,
|
|
4964
|
-
required: [
|
|
4997
|
+
required: [
|
|
4998
|
+
"rawSwearCount",
|
|
4999
|
+
"swearingMessagePercent",
|
|
5000
|
+
"averageMaxCumulativeRage",
|
|
5001
|
+
"maxCumulativeRage"
|
|
5002
|
+
],
|
|
4965
5003
|
properties: {
|
|
4966
5004
|
rawSwearCount: { type: "number" },
|
|
4967
|
-
swearingMessagePercent: { type: "number" }
|
|
5005
|
+
swearingMessagePercent: { type: "number" },
|
|
5006
|
+
averageMaxCumulativeRage: { type: "number" },
|
|
5007
|
+
maxCumulativeRage: { type: "number" }
|
|
4968
5008
|
}
|
|
4969
5009
|
},
|
|
4970
5010
|
sync: {
|
|
@@ -280,21 +280,31 @@ export const devrageMetricPayloadSchema = z.object({
|
|
|
280
280
|
latestDateKey: z.string().nullable(),
|
|
281
281
|
rawSwearCount: z.number().nonnegative(),
|
|
282
282
|
swearingMessagePercent: z.number().nonnegative(),
|
|
283
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
284
|
+
maxCumulativeRage: z.number().nonnegative(),
|
|
285
|
+
maxSwearingStreak: z.number().int().nonnegative(),
|
|
283
286
|
conversationsScanned: z.number().int().nonnegative(),
|
|
284
287
|
messagesScanned: z.number().int().nonnegative(),
|
|
285
288
|
messagesWithSwears: z.number().int().nonnegative(),
|
|
286
289
|
dailyAverage: z.object({
|
|
287
290
|
rawSwearCount: z.number().nonnegative(),
|
|
288
|
-
swearingMessagePercent: z.number().nonnegative()
|
|
291
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
292
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
293
|
+
maxCumulativeRage: z.number().nonnegative()
|
|
289
294
|
}),
|
|
290
295
|
weeklyAverage: z.object({
|
|
291
296
|
rawSwearCount: z.number().nonnegative(),
|
|
292
|
-
swearingMessagePercent: z.number().nonnegative()
|
|
297
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
298
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
299
|
+
maxCumulativeRage: z.number().nonnegative()
|
|
293
300
|
}),
|
|
294
301
|
history: z.array(z.object({
|
|
295
302
|
dateKey: z.string(),
|
|
296
303
|
rawSwearCount: z.number().nonnegative(),
|
|
297
304
|
swearingMessagePercent: z.number().nonnegative(),
|
|
305
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
306
|
+
maxCumulativeRage: z.number().nonnegative(),
|
|
307
|
+
maxSwearingStreak: z.number().int().nonnegative(),
|
|
298
308
|
conversationsScanned: z.number().int().nonnegative(),
|
|
299
309
|
messagesScanned: z.number().int().nonnegative(),
|
|
300
310
|
messagesWithSwears: z.number().int().nonnegative()
|
|
@@ -337,11 +347,15 @@ export const psycheMetricsViewDataSchema = z.object({
|
|
|
337
347
|
totalSwears: z.number().nonnegative(),
|
|
338
348
|
dailyAverage: z.object({
|
|
339
349
|
rawSwearCount: z.number().nonnegative(),
|
|
340
|
-
swearingMessagePercent: z.number().nonnegative()
|
|
350
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
351
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
352
|
+
maxCumulativeRage: z.number().nonnegative()
|
|
341
353
|
}),
|
|
342
354
|
weeklyAverage: z.object({
|
|
343
355
|
rawSwearCount: z.number().nonnegative(),
|
|
344
|
-
swearingMessagePercent: z.number().nonnegative()
|
|
356
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
357
|
+
averageMaxCumulativeRage: z.number().nonnegative(),
|
|
358
|
+
maxCumulativeRage: z.number().nonnegative()
|
|
345
359
|
}),
|
|
346
360
|
sync: z.object({
|
|
347
361
|
fullSyncCompletedAt: z.string().nullable(),
|
|
@@ -7,7 +7,8 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
const tokenPattern = /[a-z][a-z0-9'*_-]*/gi;
|
|
9
9
|
const defaultSwearLexicon = [
|
|
10
|
-
{ root: "fuck", variants: ["fuck", "fucked", "fucker", "fuckers", "fuckin", "fucking", "fucks", "motherfuck", "motherfucked", "motherfucker", "motherfuckers", "motherfucking"] },
|
|
10
|
+
{ root: "fuck", variants: ["fuck", "f*ck", "f**k", "fck", "fuk", "fucked", "fucker", "fuckers", "fuckin", "fucking", "fucks", "motherfuck", "motherfucked", "motherfucker", "motherfuckers", "motherfucking"] },
|
|
11
|
+
{ root: "ffs", variants: ["ffs", "for fucks sake", "for fuck's sake", "for-fucks-sake", "for-fuck's-sake"] },
|
|
11
12
|
{ root: "wtf", variants: ["wtf"] },
|
|
12
13
|
{ root: "shit", variants: ["shit", "shitshow", "shits", "shitty", "bullshit", "bullshitting", "dipshit", "dipshits"] },
|
|
13
14
|
{ root: "dick", variants: ["dick", "dicks", "dickhead", "dickheads"] },
|
|
@@ -33,10 +34,7 @@ const ADAPTER_FACTORIES = {
|
|
|
33
34
|
join(homedir(), ".hermes", "**/*.{json,jsonl}"),
|
|
34
35
|
join(homedir(), ".config", "hermes", "**/*.{json,jsonl}")
|
|
35
36
|
]),
|
|
36
|
-
openclaw:
|
|
37
|
-
join(homedir(), ".openclaw", "**/*.{json,jsonl}"),
|
|
38
|
-
join(homedir(), "Library", "Application Support", "OpenClaw", "**/*.{json,jsonl}")
|
|
39
|
-
]),
|
|
37
|
+
openclaw: openclawAdapter,
|
|
40
38
|
opencode: opencodeAdapter,
|
|
41
39
|
zed: zedAdapter
|
|
42
40
|
};
|
|
@@ -69,7 +67,7 @@ function createAdapter(source) {
|
|
|
69
67
|
function allAdapters() {
|
|
70
68
|
return availableSources().map((source) => createAdapter(source));
|
|
71
69
|
}
|
|
72
|
-
function analyzeConversations(conversations, options, generatedAt = new Date().toISOString()) {
|
|
70
|
+
export function analyzeConversations(conversations, options, generatedAt = new Date().toISOString()) {
|
|
73
71
|
const { tokenIndex, phraseVariants } = buildLexiconIndexes();
|
|
74
72
|
const agentStats = new Map();
|
|
75
73
|
const sourceStats = new Map();
|
|
@@ -89,6 +87,10 @@ function analyzeConversations(conversations, options, generatedAt = new Date().t
|
|
|
89
87
|
let conversationMessages = 0;
|
|
90
88
|
let conversationMessagesWithSwears = 0;
|
|
91
89
|
let conversationSwears = 0;
|
|
90
|
+
let cumulativeRage = 0;
|
|
91
|
+
let maxCumulativeRage = 0;
|
|
92
|
+
let swearingStreak = 0;
|
|
93
|
+
let maxSwearingStreak = 0;
|
|
92
94
|
const currentSource = sourceStats.get(conversation.source) ?? {
|
|
93
95
|
source: conversation.source,
|
|
94
96
|
conversations: 0,
|
|
@@ -125,7 +127,15 @@ function analyzeConversations(conversations, options, generatedAt = new Date().t
|
|
|
125
127
|
currentSource.messagesWithSwears += 1;
|
|
126
128
|
currentAgent.messagesWithSwears += 1;
|
|
127
129
|
currentAgent.swears += swearsInMessage;
|
|
130
|
+
cumulativeRage += swearsInMessage;
|
|
131
|
+
swearingStreak += 1;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
cumulativeRage = Math.max(0, cumulativeRage - 1);
|
|
135
|
+
swearingStreak = 0;
|
|
128
136
|
}
|
|
137
|
+
maxCumulativeRage = Math.max(maxCumulativeRage, cumulativeRage);
|
|
138
|
+
maxSwearingStreak = Math.max(maxSwearingStreak, swearingStreak);
|
|
129
139
|
agentStats.set(agent, currentAgent);
|
|
130
140
|
}
|
|
131
141
|
sourceStats.set(conversation.source, currentSource);
|
|
@@ -138,9 +148,17 @@ function analyzeConversations(conversations, options, generatedAt = new Date().t
|
|
|
138
148
|
dateKey,
|
|
139
149
|
messages: conversationMessages,
|
|
140
150
|
messagesWithSwears: conversationMessagesWithSwears,
|
|
141
|
-
swears: conversationSwears
|
|
151
|
+
swears: conversationSwears,
|
|
152
|
+
maxCumulativeRage,
|
|
153
|
+
maxSwearingStreak
|
|
142
154
|
});
|
|
143
155
|
}
|
|
156
|
+
const maxCumulativeRage = Math.max(0, ...conversationStats.map((conversation) => conversation.maxCumulativeRage));
|
|
157
|
+
const maxSwearingStreak = Math.max(0, ...conversationStats.map((conversation) => conversation.maxSwearingStreak));
|
|
158
|
+
const averageMaxCumulativeRage = conversationStats.length === 0
|
|
159
|
+
? 0
|
|
160
|
+
: conversationStats.reduce((sum, conversation) => sum + conversation.maxCumulativeRage, 0) /
|
|
161
|
+
conversationStats.length;
|
|
144
162
|
return {
|
|
145
163
|
generatedAt,
|
|
146
164
|
filesScanned: [...filesScanned].sort(),
|
|
@@ -148,6 +166,9 @@ function analyzeConversations(conversations, options, generatedAt = new Date().t
|
|
|
148
166
|
messagesScanned,
|
|
149
167
|
messagesWithSwears,
|
|
150
168
|
totalSwears,
|
|
169
|
+
averageMaxCumulativeRage,
|
|
170
|
+
maxCumulativeRage,
|
|
171
|
+
maxSwearingStreak,
|
|
151
172
|
byAgent: [...agentStats.entries()]
|
|
152
173
|
.map(([agent, stats]) => ({ agent, ...stats }))
|
|
153
174
|
.sort((left, right) => right.swears - left.swears ||
|
|
@@ -399,6 +420,28 @@ function genericLocalLogAdapter(source, patterns) {
|
|
|
399
420
|
}
|
|
400
421
|
};
|
|
401
422
|
}
|
|
423
|
+
function openclawAdapter() {
|
|
424
|
+
return {
|
|
425
|
+
source: "openclaw",
|
|
426
|
+
async read() {
|
|
427
|
+
const trajectoryResult = await readJsonlTree("openclaw", [
|
|
428
|
+
join(homedir(), ".openclaw", "agents"),
|
|
429
|
+
join(homedir(), "Library", "Application Support", "OpenClaw", "agents")
|
|
430
|
+
], parseOpenClawTrajectoryLine);
|
|
431
|
+
const genericResult = await genericLocalLogAdapter("openclaw", [
|
|
432
|
+
join(homedir(), ".openclaw", "**/*.{json,jsonl}"),
|
|
433
|
+
join(homedir(), "Library", "Application Support", "OpenClaw", "**/*.{json,jsonl}")
|
|
434
|
+
]).read();
|
|
435
|
+
return {
|
|
436
|
+
conversations: [
|
|
437
|
+
...trajectoryResult.conversations,
|
|
438
|
+
...genericResult.conversations.filter((conversation) => !conversation.sourceFile.endsWith(".trajectory.jsonl"))
|
|
439
|
+
],
|
|
440
|
+
warnings: [...trajectoryResult.warnings, ...genericResult.warnings]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
402
445
|
async function readJsonlTree(source, roots, parser) {
|
|
403
446
|
const files = (await Promise.all(roots.map((root) => globFiles(`${root.replace(/\/+$/, "")}/**/*.jsonl`))))
|
|
404
447
|
.flat()
|
|
@@ -515,6 +558,47 @@ function parseGenericJsonLine(record, context) {
|
|
|
515
558
|
index: context.line
|
|
516
559
|
});
|
|
517
560
|
}
|
|
561
|
+
export function parseOpenClawTrajectoryLine(record, context) {
|
|
562
|
+
if (!isObject(record) || record.type !== "prompt.submitted" || !isObject(record.data)) {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
const data = record.data;
|
|
566
|
+
const text = typeof data.prompt === "string" && data.prompt.trim().length > 0
|
|
567
|
+
? data.prompt.trim()
|
|
568
|
+
: latestOpenClawUserMessageText(data.messages);
|
|
569
|
+
if (!text || isContextInjection("user", text)) {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
agent: "openclaw",
|
|
574
|
+
source: "openclaw",
|
|
575
|
+
conversationId: context.conversationId.replace(/\.trajectory$/, ""),
|
|
576
|
+
role: "user",
|
|
577
|
+
text,
|
|
578
|
+
timestamp: stringTimestamp(record.ts) ??
|
|
579
|
+
stringTimestamp(record.timestamp) ??
|
|
580
|
+
stringTimestamp(data.timestamp) ??
|
|
581
|
+
numberTimestamp(record.ts) ??
|
|
582
|
+
context.fallbackTimestamp,
|
|
583
|
+
sourceFile: context.sourceFile
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function latestOpenClawUserMessageText(messages) {
|
|
587
|
+
if (!Array.isArray(messages)) {
|
|
588
|
+
return "";
|
|
589
|
+
}
|
|
590
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
591
|
+
const message = messages[index];
|
|
592
|
+
if (!isObject(message) || normalizeRole(message.role ?? message.type) !== "user") {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const text = extractText(message.content ?? message.text).join("\n").trim();
|
|
596
|
+
if (text) {
|
|
597
|
+
return text;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return "";
|
|
601
|
+
}
|
|
518
602
|
function parseGenericMessage(entry, context) {
|
|
519
603
|
if (!isObject(entry)) {
|
|
520
604
|
return null;
|
|
@@ -590,7 +674,7 @@ function buildLexiconIndexes(lexicon = defaultSwearLexicon) {
|
|
|
590
674
|
});
|
|
591
675
|
continue;
|
|
592
676
|
}
|
|
593
|
-
tokenIndex.set(normalizedVariant, entry.root);
|
|
677
|
+
tokenIndex.set(normalizeToken(normalizedVariant), entry.root);
|
|
594
678
|
}
|
|
595
679
|
}
|
|
596
680
|
phraseVariants.sort((left, right) => right.variant.length - left.variant.length || left.variant.localeCompare(right.variant));
|
|
@@ -676,17 +760,38 @@ function buildDailyStats(conversations) {
|
|
|
676
760
|
messages: 0,
|
|
677
761
|
messagesWithSwears: 0,
|
|
678
762
|
swears: 0,
|
|
679
|
-
swearingMessagePercent: 0
|
|
763
|
+
swearingMessagePercent: 0,
|
|
764
|
+
averageMaxCumulativeRage: 0,
|
|
765
|
+
maxCumulativeRage: 0,
|
|
766
|
+
maxSwearingStreak: 0,
|
|
767
|
+
maxCumulativeRageSum: 0
|
|
680
768
|
};
|
|
681
769
|
current.conversations += 1;
|
|
682
770
|
current.messages += conversation.messages;
|
|
683
771
|
current.messagesWithSwears += conversation.messagesWithSwears;
|
|
684
772
|
current.swears += conversation.swears;
|
|
773
|
+
current.maxCumulativeRageSum += conversation.maxCumulativeRage;
|
|
774
|
+
current.maxCumulativeRage = Math.max(current.maxCumulativeRage, conversation.maxCumulativeRage);
|
|
775
|
+
current.maxSwearingStreak = Math.max(current.maxSwearingStreak, conversation.maxSwearingStreak);
|
|
685
776
|
current.swearingMessagePercent =
|
|
686
777
|
current.messages === 0 ? 0 : (current.messagesWithSwears / current.messages) * 100;
|
|
778
|
+
current.averageMaxCumulativeRage =
|
|
779
|
+
current.conversations === 0 ? 0 : current.maxCumulativeRageSum / current.conversations;
|
|
687
780
|
byDay.set(conversation.dateKey, current);
|
|
688
781
|
}
|
|
689
|
-
return [...byDay.values()]
|
|
782
|
+
return [...byDay.values()]
|
|
783
|
+
.map((stats) => ({
|
|
784
|
+
dateKey: stats.dateKey,
|
|
785
|
+
conversations: stats.conversations,
|
|
786
|
+
messages: stats.messages,
|
|
787
|
+
messagesWithSwears: stats.messagesWithSwears,
|
|
788
|
+
swears: stats.swears,
|
|
789
|
+
swearingMessagePercent: stats.swearingMessagePercent,
|
|
790
|
+
averageMaxCumulativeRage: stats.averageMaxCumulativeRage,
|
|
791
|
+
maxCumulativeRage: stats.maxCumulativeRage,
|
|
792
|
+
maxSwearingStreak: stats.maxSwearingStreak
|
|
793
|
+
}))
|
|
794
|
+
.sort((left, right) => right.dateKey.localeCompare(left.dateKey));
|
|
690
795
|
}
|
|
691
796
|
function compactMessage(message) {
|
|
692
797
|
return message ? [message] : [];
|
|
@@ -4,6 +4,8 @@ import { getDatabase, runInTransaction } from "../db.js";
|
|
|
4
4
|
import { psycheMetricsViewDataSchema } from "../psyche-types.js";
|
|
5
5
|
const SWEAR_COUNT_KEY = "swear_count";
|
|
6
6
|
const SWEARING_MESSAGE_PERCENT_KEY = "swearing_message_percent";
|
|
7
|
+
const AVERAGE_MAX_CUMULATIVE_RAGE_KEY = "average_max_cumulative_rage";
|
|
8
|
+
const MAX_CUMULATIVE_RAGE_KEY = "max_cumulative_rage";
|
|
7
9
|
const DEFAULT_ROLE_FILTER = new Set(["user"]);
|
|
8
10
|
const DAILY_RESYNC_INTERVAL_MS = 60 * 60 * 1000;
|
|
9
11
|
const PSYCHE_METRIC_DEFINITIONS = {
|
|
@@ -20,6 +22,20 @@ const PSYCHE_METRIC_DEFINITIONS = {
|
|
|
20
22
|
category: "conversationTone",
|
|
21
23
|
unit: "%",
|
|
22
24
|
aggregation: "discrete"
|
|
25
|
+
},
|
|
26
|
+
[AVERAGE_MAX_CUMULATIVE_RAGE_KEY]: {
|
|
27
|
+
metric: "devrageAverageMaxCumulativeRage",
|
|
28
|
+
label: "Average max cumulative rage",
|
|
29
|
+
category: "conversationTone",
|
|
30
|
+
unit: "score",
|
|
31
|
+
aggregation: "discrete"
|
|
32
|
+
},
|
|
33
|
+
[MAX_CUMULATIVE_RAGE_KEY]: {
|
|
34
|
+
metric: "devrageMaxCumulativeRage",
|
|
35
|
+
label: "Max cumulative rage",
|
|
36
|
+
category: "conversationTone",
|
|
37
|
+
unit: "score",
|
|
38
|
+
aggregation: "discrete"
|
|
23
39
|
}
|
|
24
40
|
};
|
|
25
41
|
let syncInFlight = null;
|
|
@@ -69,16 +85,23 @@ export function getDevrageMetricPayload() {
|
|
|
69
85
|
latestDateKey: latest?.dateKey ?? null,
|
|
70
86
|
rawSwearCount: latest?.rawSwearCount ?? 0,
|
|
71
87
|
swearingMessagePercent: latest?.swearingMessagePercent ?? 0,
|
|
88
|
+
averageMaxCumulativeRage: latest?.averageMaxCumulativeRage ?? 0,
|
|
89
|
+
maxCumulativeRage: latest?.maxCumulativeRage ?? 0,
|
|
90
|
+
maxSwearingStreak: latest?.maxSwearingStreak ?? 0,
|
|
72
91
|
conversationsScanned: latest?.conversationsScanned ?? 0,
|
|
73
92
|
messagesScanned: latest?.messagesScanned ?? 0,
|
|
74
93
|
messagesWithSwears: latest?.messagesWithSwears ?? 0,
|
|
75
94
|
dailyAverage: {
|
|
76
95
|
rawSwearCount: dailyAverages.rawSwearCount,
|
|
77
|
-
swearingMessagePercent: dailyAverages.swearingMessagePercent
|
|
96
|
+
swearingMessagePercent: dailyAverages.swearingMessagePercent,
|
|
97
|
+
averageMaxCumulativeRage: dailyAverages.averageMaxCumulativeRage,
|
|
98
|
+
maxCumulativeRage: dailyAverages.maxCumulativeRage
|
|
78
99
|
},
|
|
79
100
|
weeklyAverage: {
|
|
80
101
|
rawSwearCount: weeklyAverages.rawSwearCount,
|
|
81
|
-
swearingMessagePercent: weeklyAverages.swearingMessagePercent
|
|
102
|
+
swearingMessagePercent: weeklyAverages.swearingMessagePercent,
|
|
103
|
+
averageMaxCumulativeRage: weeklyAverages.averageMaxCumulativeRage,
|
|
104
|
+
maxCumulativeRage: weeklyAverages.maxCumulativeRage
|
|
82
105
|
},
|
|
83
106
|
history,
|
|
84
107
|
sync: {
|
|
@@ -220,11 +243,15 @@ export function getPsycheMetricsViewData() {
|
|
|
220
243
|
totalSwears: Number(context.swear_count) || 0,
|
|
221
244
|
dailyAverage: {
|
|
222
245
|
rawSwearCount: dailyAverages.rawSwearCount,
|
|
223
|
-
swearingMessagePercent: dailyAverages.swearingMessagePercent
|
|
246
|
+
swearingMessagePercent: dailyAverages.swearingMessagePercent,
|
|
247
|
+
averageMaxCumulativeRage: dailyAverages.averageMaxCumulativeRage,
|
|
248
|
+
maxCumulativeRage: dailyAverages.maxCumulativeRage
|
|
224
249
|
},
|
|
225
250
|
weeklyAverage: {
|
|
226
251
|
rawSwearCount: weeklyAverages.rawSwearCount,
|
|
227
|
-
swearingMessagePercent: weeklyAverages.swearingMessagePercent
|
|
252
|
+
swearingMessagePercent: weeklyAverages.swearingMessagePercent,
|
|
253
|
+
averageMaxCumulativeRage: weeklyAverages.averageMaxCumulativeRage,
|
|
254
|
+
maxCumulativeRage: weeklyAverages.maxCumulativeRage
|
|
228
255
|
},
|
|
229
256
|
sync: {
|
|
230
257
|
fullSyncCompletedAt: state?.full_sync_completed_at ?? null,
|
|
@@ -255,20 +282,23 @@ export function storeDevrageReport(report, options) {
|
|
|
255
282
|
const deleteDate = database.prepare(`DELETE FROM psyche_devrage_conversation_measures WHERE date_key = ?`);
|
|
256
283
|
const insertConversation = database.prepare(`INSERT INTO psyche_devrage_conversation_measures (
|
|
257
284
|
id, source, conversation_id, date_key, updated_at, messages,
|
|
258
|
-
messages_with_swears, swear_count,
|
|
285
|
+
messages_with_swears, swear_count, max_cumulative_rage,
|
|
286
|
+
max_swearing_streak, scanned_at
|
|
259
287
|
)
|
|
260
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
288
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
261
289
|
ON CONFLICT(source, conversation_id, date_key) DO UPDATE SET
|
|
262
290
|
updated_at = excluded.updated_at,
|
|
263
291
|
messages = excluded.messages,
|
|
264
292
|
messages_with_swears = excluded.messages_with_swears,
|
|
265
293
|
swear_count = excluded.swear_count,
|
|
294
|
+
max_cumulative_rage = excluded.max_cumulative_rage,
|
|
295
|
+
max_swearing_streak = excluded.max_swearing_streak,
|
|
266
296
|
scanned_at = excluded.scanned_at`);
|
|
267
297
|
for (const dateKey of affectedDateKeys) {
|
|
268
298
|
deleteDate.run(dateKey);
|
|
269
299
|
}
|
|
270
300
|
for (const conversation of report.conversations) {
|
|
271
|
-
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);
|
|
301
|
+
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, conversation.maxCumulativeRage, conversation.maxSwearingStreak, scannedAt);
|
|
272
302
|
}
|
|
273
303
|
for (const dateKey of affectedDateKeys) {
|
|
274
304
|
recomputeMetricMeasuresForDate(dateKey, scannedAt);
|
|
@@ -287,16 +317,23 @@ function recomputeMetricMeasuresForDate(dateKey, computedAt) {
|
|
|
287
317
|
COUNT(*) AS conversations,
|
|
288
318
|
COALESCE(SUM(messages), 0) AS messages,
|
|
289
319
|
COALESCE(SUM(messages_with_swears), 0) AS messages_with_swears,
|
|
290
|
-
COALESCE(SUM(swear_count), 0) AS swear_count
|
|
320
|
+
COALESCE(SUM(swear_count), 0) AS swear_count,
|
|
321
|
+
COALESCE(AVG(max_cumulative_rage), 0) AS average_max_cumulative_rage,
|
|
322
|
+
COALESCE(MAX(max_cumulative_rage), 0) AS max_cumulative_rage,
|
|
323
|
+
COALESCE(MAX(max_swearing_streak), 0) AS max_swearing_streak
|
|
291
324
|
FROM psyche_devrage_conversation_measures
|
|
292
325
|
WHERE date_key = ?`)
|
|
293
326
|
.get(dateKey);
|
|
294
327
|
const messages = Number(aggregate.messages) || 0;
|
|
295
328
|
const messagesWithSwears = Number(aggregate.messages_with_swears) || 0;
|
|
296
329
|
const swearCount = Number(aggregate.swear_count) || 0;
|
|
330
|
+
const averageMaxCumulativeRage = Number(aggregate.average_max_cumulative_rage) || 0;
|
|
331
|
+
const maxCumulativeRage = Number(aggregate.max_cumulative_rage) || 0;
|
|
297
332
|
const percent = messages > 0 ? (messagesWithSwears / messages) * 100 : 0;
|
|
298
333
|
upsertMetricMeasure(dateKey, SWEAR_COUNT_KEY, swearCount, "count", Number(aggregate.conversations) || 0, computedAt);
|
|
299
334
|
upsertMetricMeasure(dateKey, SWEARING_MESSAGE_PERCENT_KEY, percent, "percent", messages, computedAt);
|
|
335
|
+
upsertMetricMeasure(dateKey, AVERAGE_MAX_CUMULATIVE_RAGE_KEY, averageMaxCumulativeRage, "score", Number(aggregate.conversations) || 0, computedAt);
|
|
336
|
+
upsertMetricMeasure(dateKey, MAX_CUMULATIVE_RAGE_KEY, maxCumulativeRage, "score", Number(aggregate.conversations) || 0, computedAt);
|
|
300
337
|
}
|
|
301
338
|
function upsertMetricMeasure(dateKey, metricKey, value, unit, sampleCount, computedAt) {
|
|
302
339
|
getDatabase()
|
|
@@ -339,7 +376,10 @@ function getDevrageDailyHistory(limit) {
|
|
|
339
376
|
COUNT(*) AS conversations,
|
|
340
377
|
COALESCE(SUM(messages), 0) AS messages,
|
|
341
378
|
COALESCE(SUM(messages_with_swears), 0) AS messages_with_swears,
|
|
342
|
-
COALESCE(SUM(swear_count), 0) AS swear_count
|
|
379
|
+
COALESCE(SUM(swear_count), 0) AS swear_count,
|
|
380
|
+
COALESCE(AVG(max_cumulative_rage), 0) AS average_max_cumulative_rage,
|
|
381
|
+
COALESCE(MAX(max_cumulative_rage), 0) AS max_cumulative_rage,
|
|
382
|
+
COALESCE(MAX(max_swearing_streak), 0) AS max_swearing_streak
|
|
343
383
|
FROM psyche_devrage_conversation_measures
|
|
344
384
|
GROUP BY date_key
|
|
345
385
|
ORDER BY date_key DESC
|
|
@@ -352,6 +392,9 @@ function getDevrageDailyHistory(limit) {
|
|
|
352
392
|
dateKey: row.date_key,
|
|
353
393
|
rawSwearCount: Number(row.swear_count) || 0,
|
|
354
394
|
swearingMessagePercent: messages > 0 ? (messagesWithSwears / messages) * 100 : 0,
|
|
395
|
+
averageMaxCumulativeRage: Number(row.average_max_cumulative_rage) || 0,
|
|
396
|
+
maxCumulativeRage: Number(row.max_cumulative_rage) || 0,
|
|
397
|
+
maxSwearingStreak: Number(row.max_swearing_streak) || 0,
|
|
355
398
|
conversationsScanned: Number(row.conversations) || 0,
|
|
356
399
|
messagesScanned: messages,
|
|
357
400
|
messagesWithSwears
|
|
@@ -375,9 +418,13 @@ function getMetricAverages(days) {
|
|
|
375
418
|
.all(...(days ? [days] : []));
|
|
376
419
|
const swearAverage = rows.find((row) => row.metric_key === SWEAR_COUNT_KEY)?.value ?? 0;
|
|
377
420
|
const percentAverage = rows.find((row) => row.metric_key === SWEARING_MESSAGE_PERCENT_KEY)?.value ?? 0;
|
|
421
|
+
const averageMaxCumulativeRage = rows.find((row) => row.metric_key === AVERAGE_MAX_CUMULATIVE_RAGE_KEY)?.value ?? 0;
|
|
422
|
+
const maxCumulativeRage = rows.find((row) => row.metric_key === MAX_CUMULATIVE_RAGE_KEY)?.value ?? 0;
|
|
378
423
|
return {
|
|
379
424
|
rawSwearCount: round(Number(swearAverage) || 0, 1),
|
|
380
|
-
swearingMessagePercent: round(Number(percentAverage) || 0, 1)
|
|
425
|
+
swearingMessagePercent: round(Number(percentAverage) || 0, 1),
|
|
426
|
+
averageMaxCumulativeRage: round(Number(averageMaxCumulativeRage) || 0, 1),
|
|
427
|
+
maxCumulativeRage: round(Number(maxCumulativeRage) || 0, 1)
|
|
381
428
|
};
|
|
382
429
|
}
|
|
383
430
|
function getDevrageConversationTotals() {
|
|
@@ -387,7 +434,10 @@ function getDevrageConversationTotals() {
|
|
|
387
434
|
COUNT(DISTINCT source) AS sources,
|
|
388
435
|
COALESCE(SUM(messages), 0) AS messages,
|
|
389
436
|
COALESCE(SUM(messages_with_swears), 0) AS messages_with_swears,
|
|
390
|
-
COALESCE(SUM(swear_count), 0) AS swear_count
|
|
437
|
+
COALESCE(SUM(swear_count), 0) AS swear_count,
|
|
438
|
+
COALESCE(AVG(max_cumulative_rage), 0) AS average_max_cumulative_rage,
|
|
439
|
+
COALESCE(MAX(max_cumulative_rage), 0) AS max_cumulative_rage,
|
|
440
|
+
COALESCE(MAX(max_swearing_streak), 0) AS max_swearing_streak
|
|
391
441
|
FROM psyche_devrage_conversation_measures`)
|
|
392
442
|
.get();
|
|
393
443
|
}
|
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.3.
|
|
5
|
+
"version": "0.3.1",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED
|
@@ -265,6 +265,12 @@ Entity conversation rule:
|
|
|
265
265
|
- When the user wants to review, compare, inspect, or navigate an existing Forge
|
|
266
266
|
record, ask what they are trying to understand first and look up the existing record
|
|
267
267
|
before you reopen create or update intake.
|
|
268
|
+
- For review-first requests, use the correct read posture before asking write-shaped
|
|
269
|
+
questions: shared batch search or read hints for normal entities, wiki/calendar
|
|
270
|
+
dedicated reads for specialized CRUD, read-model routes for overviews, and
|
|
271
|
+
Movement, Life Force, or Workbench dedicated reads for those domain surfaces. After
|
|
272
|
+
the read, answer the practical question before asking for any save, correction,
|
|
273
|
+
link, run, enrichment, or publish detail.
|
|
268
274
|
- When updating an entity, start with what is changing, what should stay true, and what prompted the update now.
|
|
269
275
|
- When enough is clear, briefly summarize what you heard in the user's own language before asking for the last missing structural detail.
|
|
270
276
|
- Treat `userId` and human/bot assignees as accountability and scope, not as opening form fields. Ask whose human or bot record it is only when ownership changes visibility, review scope, collaboration, automation behavior, or later filtering; for read requests, ask user scope only when the answer would differ across owners.
|
|
@@ -294,6 +300,7 @@ Psyche interview rule:
|
|
|
294
300
|
- After the first real answer, choose one follow-up lane at a time: situation, sequence, meaning, protection, cost, longing/value, or tentative name.
|
|
295
301
|
- Do not minimize functional analysis, trigger chains, behavior patterns, modes, beliefs, or schema themes. Once at least one concrete example is clear, offer one careful interpretive hypothesis when it would help the user understand the function, protection, cost, belief, mode, or schema theme.
|
|
296
302
|
- Phrase interpretive hypotheses as collaborative and testable, not as verdicts. A good hypothesis says what the reaction may be protecting, predicting, relieving, or costing, then asks whether that lands or needs correction.
|
|
303
|
+
- Use the hypothesis timing checkpoint before asking a second or third deepening question: offer a hypothesis when one concrete episode, body cue, belief sentence, behavior, or mode voice is visible and the hypothesis would change the record shape, wording, links, or next action. Do not hypothesize yet when no concrete moment is visible, the user only wants a direct mechanical save, the user is flooded or unsafe, or the only available interpretation would be diagnosis-like, an origin story, or a certainty claim.
|
|
297
304
|
- If several Psyche containers are plausible, do not ask the user to choose from a taxonomy menu first. Reflect the lived difference, offer one careful hypothesis when a concrete example is visible, then distinguish the options in plain language: one episode as a `trigger_report`, a recurring loop as a `behavior_pattern`, one repeated move as `behavior`, one sentence as `belief_entry`, a part-state as `mode_profile` or `mode_guide_session`, or reusable future-labeling as `event_type` or `emotion_definition`.
|
|
298
305
|
- For Psyche updates, start with what feels newly true, newly visible, or newly inaccurate, then ask what should stay true before changing the formulation.
|
|
299
306
|
- If a fresh episode is what made a Psyche update visible, anchor in that episode before renaming the durable belief, pattern, mode, or value.
|