@haaaiawd/second-nature 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/index.js +855 -855
  2. package/openclaw.plugin.json +29 -29
  3. package/package.json +52 -52
  4. package/runtime/cli/commands/index.d.ts +14 -14
  5. package/runtime/cli/commands/index.js +193 -193
  6. package/runtime/cli/explain/explain-surface-subject.d.ts +8 -8
  7. package/runtime/cli/explain/explain-surface-subject.js +9 -9
  8. package/runtime/cli/explain/format-explanation.d.ts +12 -12
  9. package/runtime/cli/explain/format-explanation.js +12 -12
  10. package/runtime/cli/explain/resolve-subject.js +41 -41
  11. package/runtime/cli/host-capability/classify-delivery.d.ts +14 -14
  12. package/runtime/cli/host-capability/classify-delivery.js +20 -20
  13. package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -2
  14. package/runtime/cli/host-capability/probe-host-capability.js +58 -58
  15. package/runtime/cli/host-capability/record-host-capability.d.ts +6 -6
  16. package/runtime/cli/host-capability/record-host-capability.js +14 -14
  17. package/runtime/cli/host-capability/types.d.ts +71 -71
  18. package/runtime/cli/host-capability/types.js +6 -6
  19. package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -2
  20. package/runtime/cli/host-smoke/run-host-smoke.js +40 -40
  21. package/runtime/cli/host-smoke/types.d.ts +35 -35
  22. package/runtime/cli/host-smoke/types.js +6 -6
  23. package/runtime/cli/index.js +65 -58
  24. package/runtime/cli/ops/heartbeat-surface.d.ts +45 -38
  25. package/runtime/cli/ops/heartbeat-surface.js +79 -73
  26. package/runtime/cli/ops/ops-router.d.ts +26 -19
  27. package/runtime/cli/ops/ops-router.js +102 -89
  28. package/runtime/cli/ops/show-operator-fallback.d.ts +13 -13
  29. package/runtime/cli/ops/show-operator-fallback.js +22 -22
  30. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +40 -19
  31. package/runtime/cli/ops/workspace-heartbeat-runner.js +93 -39
  32. package/runtime/cli/read-models/index.d.ts +35 -29
  33. package/runtime/cli/read-models/index.js +365 -256
  34. package/runtime/cli/read-models/operator-explain-map.d.ts +6 -6
  35. package/runtime/cli/read-models/operator-explain-map.js +10 -10
  36. package/runtime/cli/read-models/types.d.ts +112 -79
  37. package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -28
  38. package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -94
  39. package/runtime/connectors/base/contract.d.ts +87 -87
  40. package/runtime/connectors/base/execution-policy.d.ts +47 -47
  41. package/runtime/connectors/base/execution-policy.js +82 -82
  42. package/runtime/connectors/base/index.d.ts +8 -8
  43. package/runtime/connectors/base/index.js +8 -8
  44. package/runtime/connectors/base/manifest.d.ts +64 -64
  45. package/runtime/connectors/base/manifest.js +86 -86
  46. package/runtime/connectors/base/map-life-evidence.d.ts +16 -16
  47. package/runtime/connectors/base/map-life-evidence.js +79 -79
  48. package/runtime/connectors/base/policy-layer.d.ts +29 -29
  49. package/runtime/connectors/base/policy-layer.js +198 -198
  50. package/runtime/connectors/base/route-planner.js +99 -99
  51. package/runtime/connectors/index.d.ts +5 -5
  52. package/runtime/connectors/index.js +5 -5
  53. package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -19
  54. package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -152
  55. package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +114 -114
  56. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +63 -63
  57. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +162 -139
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +8 -8
  59. package/runtime/core/second-nature/heartbeat/index.js +7 -7
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -21
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -35
  62. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -28
  63. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -35
  64. package/runtime/core/second-nature/heartbeat/signal.d.ts +42 -42
  65. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +51 -51
  66. package/runtime/core/second-nature/index.d.ts +22 -22
  67. package/runtime/core/second-nature/index.js +22 -22
  68. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +100 -100
  69. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +144 -144
  70. package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +8 -8
  71. package/runtime/core/second-nature/orchestrator/guard-layer.js +110 -110
  72. package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +13 -13
  73. package/runtime/core/second-nature/orchestrator/intent-planner.js +199 -199
  74. package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +14 -14
  75. package/runtime/core/second-nature/orchestrator/lease-manager.js +58 -58
  76. package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -6
  77. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -63
  78. package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -26
  79. package/runtime/core/second-nature/outreach/delivery-target.js +70 -70
  80. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -38
  81. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -119
  82. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -7
  83. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -45
  84. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -40
  85. package/runtime/core/second-nature/outreach/judge-outreach.js +121 -121
  86. package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -21
  87. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -123
  88. package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -15
  89. package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -52
  90. package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -19
  91. package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -34
  92. package/runtime/core/second-nature/runtime/service-entry.js +45 -45
  93. package/runtime/core/second-nature/types.d.ts +51 -51
  94. package/runtime/guidance/draft-outreach-message.d.ts +7 -7
  95. package/runtime/guidance/draft-outreach-message.js +42 -42
  96. package/runtime/guidance/evidence-guidance.d.ts +40 -40
  97. package/runtime/guidance/evidence-guidance.js +52 -52
  98. package/runtime/guidance/index.d.ts +11 -11
  99. package/runtime/guidance/index.js +11 -11
  100. package/runtime/guidance/outreach-draft-schema.d.ts +228 -228
  101. package/runtime/guidance/outreach-draft-schema.js +80 -80
  102. package/runtime/observability/audit/append-only-audit-store.d.ts +14 -14
  103. package/runtime/observability/audit/append-only-audit-store.js +21 -21
  104. package/runtime/observability/audit/audit-envelope.d.ts +51 -51
  105. package/runtime/observability/audit/audit-envelope.js +130 -130
  106. package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -23
  107. package/runtime/observability/audit/verify-audit-hash-chain.js +83 -83
  108. package/runtime/observability/db/index.js +47 -47
  109. package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -180
  110. package/runtime/observability/db/schema/host-capability-reports.js +12 -12
  111. package/runtime/observability/db/schema/index.d.ts +947 -947
  112. package/runtime/observability/db/schema/index.js +71 -71
  113. package/runtime/observability/index.d.ts +20 -20
  114. package/runtime/observability/index.js +19 -19
  115. package/runtime/observability/query/explain-query.d.ts +48 -48
  116. package/runtime/observability/query/explain-query.js +114 -114
  117. package/runtime/observability/query/export-audit-bundle.d.ts +22 -22
  118. package/runtime/observability/query/export-audit-bundle.js +27 -27
  119. package/runtime/observability/services/decision-ledger.d.ts +46 -46
  120. package/runtime/observability/services/decision-ledger.js +161 -161
  121. package/runtime/observability/services/governance-audit.d.ts +41 -41
  122. package/runtime/observability/services/governance-audit.js +163 -163
  123. package/runtime/observability/services/governance-plane-recorder.d.ts +47 -47
  124. package/runtime/observability/services/governance-plane-recorder.js +55 -55
  125. package/runtime/observability/services/lived-experience-audit.d.ts +97 -97
  126. package/runtime/observability/services/lived-experience-audit.js +162 -162
  127. package/runtime/observability/services/runtime-decision-recorder.d.ts +29 -29
  128. package/runtime/observability/services/runtime-decision-recorder.js +94 -94
  129. package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -7
  130. package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -28
  131. package/runtime/storage/bootstrap/repair-gate.d.ts +17 -17
  132. package/runtime/storage/bootstrap/repair-gate.js +71 -71
  133. package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -38
  134. package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -85
  135. package/runtime/storage/db/index.js +61 -61
  136. package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -199
  137. package/runtime/storage/db/schema/delivery-attempts.js +13 -13
  138. package/runtime/storage/db/schema/index.d.ts +9 -9
  139. package/runtime/storage/db/schema/index.js +9 -9
  140. package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -161
  141. package/runtime/storage/db/schema/life-evidence-index.js +11 -11
  142. package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -161
  143. package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -11
  144. package/runtime/storage/db/schema/policies.d.ts +98 -98
  145. package/runtime/storage/db/schema/policies.js +8 -8
  146. package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -3
  147. package/runtime/storage/delivery/query-delivery-attempts.js +32 -32
  148. package/runtime/storage/delivery/types.d.ts +27 -27
  149. package/runtime/storage/delivery/types.js +1 -1
  150. package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -6
  151. package/runtime/storage/delivery/write-delivery-attempt.js +36 -36
  152. package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -14
  153. package/runtime/storage/fallback/load-operator-fallback.js +47 -47
  154. package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -9
  155. package/runtime/storage/fallback/operator-fallback-types.js +1 -1
  156. package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -11
  157. package/runtime/storage/fallback/operator-fallback-view.js +1 -1
  158. package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -6
  159. package/runtime/storage/fallback/write-operator-fallback.js +21 -21
  160. package/runtime/storage/index.d.ts +37 -37
  161. package/runtime/storage/index.js +30 -30
  162. package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -7
  163. package/runtime/storage/life-evidence/append-life-evidence.js +64 -64
  164. package/runtime/storage/life-evidence/types.d.ts +45 -45
  165. package/runtime/storage/life-evidence/types.js +6 -6
  166. package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -7
  167. package/runtime/storage/quiet/persist-quiet-artifact.js +22 -22
  168. package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -18
  169. package/runtime/storage/quiet/quiet-artifact-types.js +1 -1
  170. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -15
  171. package/runtime/storage/quiet/quiet-artifact-writer.js +56 -56
  172. package/runtime/storage/repositories/credential-repository.js +30 -30
  173. package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -10
  174. package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -34
  175. package/runtime/storage/services/credential-vault.d.ts +13 -13
  176. package/runtime/storage/services/credential-vault.js +116 -116
  177. package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -9
  178. package/runtime/storage/snapshots/continuity-snapshot.js +41 -41
  179. package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -6
  180. package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -114
  181. package/runtime/storage/snapshots/types.d.ts +58 -58
  182. package/runtime/storage/snapshots/types.js +1 -1
  183. package/runtime/storage/state-api.js +104 -104
  184. package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -2
  185. package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -150
  186. package/runtime/storage/user-interest/types.d.ts +25 -25
  187. package/runtime/storage/user-interest/types.js +1 -1
  188. package/workspace-ops-bridge.js +81 -81
@@ -1,256 +1,365 @@
1
- import { desc } from "drizzle-orm";
2
- import { createQuietInputLoader } from "../../storage/services/quiet-input-loader.js";
3
- import { AssetRepository } from "../../storage/repositories/asset-repository.js";
4
- import { CredentialRepository } from "../../storage/repositories/credential-repository.js";
5
- import { EvidenceQueryEngine } from "../../observability/query/evidence-query-engine.js";
6
- import { decisionLedger, executionAttempts } from "../../observability/db/schema/index.js";
7
- import { queryExplain } from "../../observability/query/explain-query.js";
8
- import { mapOperatorExplainToReadModel } from "./operator-explain-map.js";
9
- import { loadOperatorFallbackRow, toOperatorFallbackView } from "../../storage/fallback/load-operator-fallback.js";
10
- const INTERNAL_RUNTIME_PLATFORM_ID = "second-nature-runtime";
11
- const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
12
- function toExplainQuery(subject) {
13
- switch (subject.kind) {
14
- case "decision":
15
- return { kind: "decision", decisionId: subject.id };
16
- case "fallback": {
17
- const ref = subject.id.startsWith("fallback:") ? subject.id : `fallback:${subject.id}`;
18
- return { kind: "fallback", fallbackRef: ref };
19
- }
20
- case "probe":
21
- case "report":
22
- return { kind: "report", reportId: subject.id };
23
- case "delivery":
24
- return { kind: "delivery", auditId: subject.id };
25
- case "source_ref":
26
- return { kind: "source_ref", sourceRefId: subject.id };
27
- default:
28
- return undefined;
29
- }
30
- }
31
- function isAuditOnlySubjectKind(kind) {
32
- return kind === "fallback" || kind === "probe" || kind === "report" || kind === "delivery" || kind === "source_ref";
33
- }
34
- function buildCredentialNextStep(status) {
35
- if (status === "pending_verification")
36
- return "submit_verification_answer";
37
- if (status === "expired" || status === "revoked" || status === "failed")
38
- return "refresh_credential_context";
39
- return undefined;
40
- }
41
- function mapRuntimeStatus(attempt) {
42
- if (!attempt) {
43
- return "unknown";
44
- }
45
- if (attempt.failureClass || attempt.status === "failed") {
46
- return "degraded";
47
- }
48
- return "running";
49
- }
50
- function mapConnectorStatus(attempt) {
51
- if (!attempt) {
52
- return "unknown";
53
- }
54
- if (attempt.failureClass || attempt.status === "failed") {
55
- return "degraded";
56
- }
57
- return "healthy";
58
- }
59
- export function createCliReadModels(deps) {
60
- const assetRepository = new AssetRepository(deps.stateDb);
61
- const credentialRepository = new CredentialRepository(deps.stateDb);
62
- const quietLoader = createQuietInputLoader(assetRepository);
63
- const evidenceQuery = new EvidenceQueryEngine(deps.observabilityDb);
64
- const auditStore = deps.livedExperienceAuditStore;
65
- return {
66
- async loadStatus(_scope) {
67
- let recentAttempts = [];
68
- let recentDecisions = [];
69
- let credentials = [];
70
- try {
71
- recentAttempts = await deps.observabilityDb.db
72
- .select()
73
- .from(executionAttempts)
74
- .orderBy(desc(executionAttempts.startedAt), desc(executionAttempts.finishedAt))
75
- .limit(50);
76
- }
77
- catch {
78
- recentAttempts = [];
79
- }
80
- try {
81
- recentDecisions = await deps.observabilityDb.db
82
- .select()
83
- .from(decisionLedger)
84
- .orderBy(desc(decisionLedger.createdAt))
85
- .limit(50);
86
- }
87
- catch {
88
- recentDecisions = [];
89
- }
90
- try {
91
- credentials = await deps.stateDb.db.query.credentialRecords.findMany();
92
- }
93
- catch {
94
- credentials = [];
95
- }
96
- const latestRuntimeAttempt = recentAttempts.find((attempt) => attempt.platformId === INTERNAL_RUNTIME_PLATFORM_ID);
97
- const latestConnectorAttempt = recentAttempts.find((attempt) => attempt.platformId !== INTERNAL_RUNTIME_PLATFORM_ID);
98
- const latestRuntimeDecision = recentDecisions.find((decision) => decision.traceId.startsWith(INTERNAL_RUNTIME_TRACE_PREFIX));
99
- const runtimeUpdatedAt = latestRuntimeAttempt?.finishedAt ?? latestRuntimeAttempt?.startedAt ?? latestRuntimeDecision?.createdAt ?? "";
100
- const quietMode = latestRuntimeDecision?.mode === "quiet" ||
101
- latestRuntimeDecision?.mode === "maintenance_only" ||
102
- latestRuntimeDecision?.mode === "paused_for_interrupt"
103
- ? latestRuntimeDecision.mode
104
- : "unknown";
105
- const riskFlags = [latestRuntimeAttempt?.failureClass, latestConnectorAttempt?.failureClass].filter((value) => Boolean(value));
106
- const connectorSummary = latestConnectorAttempt
107
- ? [
108
- {
109
- platformId: latestConnectorAttempt.platformId,
110
- status: mapConnectorStatus(latestConnectorAttempt),
111
- channel: latestConnectorAttempt.channel,
112
- failureClass: latestConnectorAttempt.failureClass ?? undefined,
113
- },
114
- ]
115
- : [];
116
- return {
117
- runtime: {
118
- host: "openclaw-plugin",
119
- serviceStatus: mapRuntimeStatus(latestRuntimeAttempt),
120
- updatedAt: runtimeUpdatedAt,
121
- },
122
- rhythm: {
123
- mode: latestRuntimeDecision?.mode ?? "unknown",
124
- windowId: undefined,
125
- },
126
- quiet: {
127
- mode: quietMode,
128
- lastEvent: latestRuntimeDecision?.traceId,
129
- interrupted: latestRuntimeDecision?.mode === "paused_for_interrupt" ? true : undefined,
130
- },
131
- connectors: connectorSummary,
132
- credentials: credentials.map((item) => ({
133
- platformId: item.platformId ?? item.platform_id,
134
- status: item.status,
135
- nextStep: buildCredentialNextStep(item.status),
136
- })),
137
- risk: {
138
- level: riskFlags.length > 0 ? "medium" : "low",
139
- flags: riskFlags,
140
- },
141
- };
142
- },
143
- async loadDailyReport(day) {
144
- let bundle;
145
- try {
146
- bundle = await quietLoader.loadQuietInputs({
147
- dateRange: { start: `${day}T00:00:00.000Z`, end: `${day}T23:59:59.999Z` },
148
- assetFilters: { includeJournal: false, includeReports: true, includeCurated: false },
149
- });
150
- }
151
- catch {
152
- bundle = { dailyReports: [], journalEntries: [], sourceCount: 0 };
153
- }
154
- const report = bundle.dailyReports[0];
155
- return {
156
- day,
157
- summary: report?.summary ?? "",
158
- highlights: report?.highlights ?? [],
159
- sourceRefs: report?.sources ?? [],
160
- };
161
- },
162
- async loadQuiet(scope) {
163
- const now = new Date();
164
- const start = new Date(now);
165
- start.setDate(now.getDate() - 1);
166
- let bundle;
167
- try {
168
- bundle = await quietLoader.loadQuietInputs({
169
- dateRange: { start: start.toISOString(), end: now.toISOString() },
170
- });
171
- }
172
- catch {
173
- bundle = { dailyReports: [], journalEntries: [], sourceCount: 0 };
174
- }
175
- return {
176
- scope,
177
- mode: bundle.sourceCount > 0 ? "quiet" : "unknown",
178
- sourceCount: bundle.sourceCount,
179
- reportCount: bundle.dailyReports.length,
180
- recentJournalCount: bundle.journalEntries.length,
181
- };
182
- },
183
- async loadSession(sessionId) {
184
- const traceId = sessionId;
185
- const bundle = await evidenceQuery.queryEvidence({ traceId });
186
- return {
187
- requestedSessionId: sessionId,
188
- traceId,
189
- decisionCount: bundle.decisions.length,
190
- attemptCount: bundle.attempts.length,
191
- governanceCount: bundle.governance.length,
192
- keyFactors: bundle.explanation.keyFactors,
193
- evidenceRefs: bundle.explanation.evidenceRefs,
194
- };
195
- },
196
- async loadCredential(platformId) {
197
- let record;
198
- try {
199
- record = await credentialRepository.findByPlatformId(platformId);
200
- }
201
- catch {
202
- record = undefined;
203
- }
204
- if (!record) {
205
- return {
206
- platformId,
207
- status: "missing",
208
- nextStep: "provide_credential_context",
209
- };
210
- }
211
- return {
212
- platformId: record.platformId ?? record.platform_id,
213
- status: record.status,
214
- verificationDeadline: record.expiresAt ?? undefined,
215
- attemptsRemaining: record.attemptsRemaining ?? undefined,
216
- nextStep: buildCredentialNextStep(record.status),
217
- };
218
- },
219
- async loadFallbackView(ref) {
220
- const row = await loadOperatorFallbackRow(deps.stateDb, ref);
221
- if (!row)
222
- return null;
223
- return toOperatorFallbackView(row);
224
- },
225
- async explain(subject) {
226
- const q = toExplainQuery(subject);
227
- if (auditStore && q) {
228
- const op = queryExplain(q, auditStore);
229
- if (isAuditOnlySubjectKind(subject.kind)) {
230
- return mapOperatorExplainToReadModel(op, subject.kind);
231
- }
232
- if (op.relatedEventIds.length > 0) {
233
- return mapOperatorExplainToReadModel(op, subject.kind);
234
- }
235
- }
236
- if (isAuditOnlySubjectKind(subject.kind)) {
237
- return {
238
- subjectType: subject.kind,
239
- conclusion: auditStore ? "no_matching_audit_events" : "lived_experience_audit_store_unavailable",
240
- keyFactors: auditStore ? [] : ["configure_lived_experience_audit_store_for_operator_explain"],
241
- evidenceRefs: [],
242
- };
243
- }
244
- const query = subject.kind === "decision" || subject.kind === "platform-selection" || subject.kind === "outreach"
245
- ? { decisionId: subject.id }
246
- : { assetId: subject.id };
247
- const bundle = await evidenceQuery.queryEvidence(query);
248
- return {
249
- subjectType: subject.kind,
250
- conclusion: bundle.explanation.conclusion,
251
- keyFactors: bundle.explanation.keyFactors,
252
- evidenceRefs: bundle.explanation.evidenceRefs,
253
- };
254
- },
255
- };
256
- }
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { desc } from "drizzle-orm";
4
+ import { createQuietInputLoader } from "../../storage/services/quiet-input-loader.js";
5
+ import { AssetRepository } from "../../storage/repositories/asset-repository.js";
6
+ import { CredentialRepository } from "../../storage/repositories/credential-repository.js";
7
+ import { EvidenceQueryEngine } from "../../observability/query/evidence-query-engine.js";
8
+ import { decisionLedger, executionAttempts, } from "../../observability/db/schema/index.js";
9
+ import { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
10
+ import { queryExplain, } from "../../observability/query/explain-query.js";
11
+ import { mapOperatorExplainToReadModel } from "./operator-explain-map.js";
12
+ import { loadOperatorFallbackRow, toOperatorFallbackView, } from "../../storage/fallback/load-operator-fallback.js";
13
+ const INTERNAL_RUNTIME_PLATFORM_ID = "second-nature-runtime";
14
+ const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
15
+ function toExplainQuery(subject) {
16
+ switch (subject.kind) {
17
+ case "decision":
18
+ return { kind: "decision", decisionId: subject.id };
19
+ case "fallback": {
20
+ const ref = subject.id.startsWith("fallback:")
21
+ ? subject.id
22
+ : `fallback:${subject.id}`;
23
+ return { kind: "fallback", fallbackRef: ref };
24
+ }
25
+ case "probe":
26
+ case "report":
27
+ return { kind: "report", reportId: subject.id };
28
+ case "delivery":
29
+ return { kind: "delivery", auditId: subject.id };
30
+ case "source_ref":
31
+ return { kind: "source_ref", sourceRefId: subject.id };
32
+ default:
33
+ return undefined;
34
+ }
35
+ }
36
+ function isAuditOnlySubjectKind(kind) {
37
+ return (kind === "fallback" ||
38
+ kind === "probe" ||
39
+ kind === "report" ||
40
+ kind === "delivery" ||
41
+ kind === "source_ref");
42
+ }
43
+ function buildCredentialNextStep(status) {
44
+ if (status === "pending_verification")
45
+ return "submit_verification_answer";
46
+ if (status === "expired" || status === "revoked" || status === "failed")
47
+ return "refresh_credential_context";
48
+ return undefined;
49
+ }
50
+ /**
51
+ * T1.2.4: count persisted Quiet artifact JSON files under `.second-nature/quiet/{day}/`
52
+ * so `loadQuiet` / `loadDailyReport` can reflect Quiet artifacts in the read model.
53
+ */
54
+ function countQuietArtifactsForDay(workspaceRoot, day) {
55
+ try {
56
+ const dir = path.join(workspaceRoot, ".second-nature", "quiet", day);
57
+ if (!fs.existsSync(dir))
58
+ return 0;
59
+ return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).length;
60
+ }
61
+ catch {
62
+ return 0;
63
+ }
64
+ }
65
+ /**
66
+ * T1.2.4: scan the last N days under `.second-nature/quiet/` and count total JSON artifacts.
67
+ * Returns { totalArtifacts, recentDays } for merging into QuietReadModel.
68
+ */
69
+ function countRecentQuietArtifacts(workspaceRoot, windowDays = 2) {
70
+ try {
71
+ const quietRoot = path.join(workspaceRoot, ".second-nature", "quiet");
72
+ if (!fs.existsSync(quietRoot))
73
+ return { totalArtifacts: 0, recentDays: [] };
74
+ const now = Date.now();
75
+ const recentDays = [];
76
+ let total = 0;
77
+ for (let i = 0; i < windowDays; i++) {
78
+ const d = new Date(now - i * 86400000).toISOString().slice(0, 10);
79
+ const count = countQuietArtifactsForDay(workspaceRoot, d);
80
+ if (count > 0) {
81
+ recentDays.push(d);
82
+ total += count;
83
+ }
84
+ }
85
+ return { totalArtifacts: total, recentDays };
86
+ }
87
+ catch {
88
+ return { totalArtifacts: 0, recentDays: [] };
89
+ }
90
+ }
91
+ function mapRuntimeStatus(attempt) {
92
+ if (!attempt) {
93
+ return "unknown";
94
+ }
95
+ if (attempt.failureClass || attempt.status === "failed") {
96
+ return "degraded";
97
+ }
98
+ return "running";
99
+ }
100
+ function mapConnectorStatus(attempt) {
101
+ if (!attempt) {
102
+ return "unknown";
103
+ }
104
+ if (attempt.failureClass || attempt.status === "failed") {
105
+ return "degraded";
106
+ }
107
+ return "healthy";
108
+ }
109
+ export function createCliReadModels(deps) {
110
+ const assetRepository = new AssetRepository(deps.stateDb);
111
+ const credentialRepository = new CredentialRepository(deps.stateDb);
112
+ const quietLoader = createQuietInputLoader(assetRepository);
113
+ const evidenceQuery = new EvidenceQueryEngine(deps.observabilityDb);
114
+ // T1.2.5 (CH-14-05): default-inject an empty AppendOnlyAuditStore so `explain` does not
115
+ // immediately return `lived_experience_audit_store_unavailable` for callers that don't supply
116
+ // an explicit store. The empty store means audit-only subjects return `no_matching_audit_events`
117
+ // instead of a configuration error — which is more accurate and less alarming to operators.
118
+ const auditStore = deps.livedExperienceAuditStore ?? new AppendOnlyAuditStore();
119
+ return {
120
+ async loadStatus(_scope) {
121
+ let recentAttempts = [];
122
+ let recentDecisions = [];
123
+ let credentials = [];
124
+ try {
125
+ recentAttempts = await deps.observabilityDb.db
126
+ .select()
127
+ .from(executionAttempts)
128
+ .orderBy(desc(executionAttempts.startedAt), desc(executionAttempts.finishedAt))
129
+ .limit(50);
130
+ }
131
+ catch {
132
+ recentAttempts = [];
133
+ }
134
+ try {
135
+ recentDecisions = await deps.observabilityDb.db
136
+ .select()
137
+ .from(decisionLedger)
138
+ .orderBy(desc(decisionLedger.createdAt))
139
+ .limit(50);
140
+ }
141
+ catch {
142
+ recentDecisions = [];
143
+ }
144
+ try {
145
+ credentials = await deps.stateDb.db.query.credentialRecords.findMany();
146
+ }
147
+ catch {
148
+ credentials = [];
149
+ }
150
+ const latestRuntimeAttempt = recentAttempts.find((attempt) => attempt.platformId === INTERNAL_RUNTIME_PLATFORM_ID);
151
+ // CH-15-04 (CH-14-03): latestConnectorAttempt is the most recent execution attempt whose
152
+ // platformId is NOT the internal sn-runtime sentinel i.e. a real connector platform
153
+ // (Moltbook, EvoMap, etc.). The `connectors` array in StatusReadModel reflects this single
154
+ // most-recent non-runtime attempt, NOT the full connector manifest. An empty array means
155
+ // no connector attempt has been recorded yet, not that connectors are misconfigured.
156
+ const latestConnectorAttempt = recentAttempts.find((attempt) => attempt.platformId !== INTERNAL_RUNTIME_PLATFORM_ID);
157
+ const latestRuntimeDecision = recentDecisions.find((decision) => decision.traceId.startsWith(INTERNAL_RUNTIME_TRACE_PREFIX));
158
+ const runtimeUpdatedAt = latestRuntimeAttempt?.finishedAt ??
159
+ latestRuntimeAttempt?.startedAt ??
160
+ latestRuntimeDecision?.createdAt ??
161
+ "";
162
+ const quietMode = latestRuntimeDecision?.mode === "quiet" ||
163
+ latestRuntimeDecision?.mode === "maintenance_only" ||
164
+ latestRuntimeDecision?.mode === "paused_for_interrupt"
165
+ ? latestRuntimeDecision.mode
166
+ : "unknown";
167
+ const riskFlags = [
168
+ latestRuntimeAttempt?.failureClass,
169
+ latestConnectorAttempt?.failureClass,
170
+ ].filter((value) => Boolean(value));
171
+ const connectorSummary = latestConnectorAttempt
172
+ ? [
173
+ {
174
+ platformId: latestConnectorAttempt.platformId,
175
+ status: mapConnectorStatus(latestConnectorAttempt),
176
+ channel: latestConnectorAttempt.channel,
177
+ failureClass: latestConnectorAttempt.failureClass ?? undefined,
178
+ },
179
+ ]
180
+ : [];
181
+ return {
182
+ runtime: {
183
+ host: "openclaw-plugin",
184
+ serviceStatus: mapRuntimeStatus(latestRuntimeAttempt),
185
+ updatedAt: runtimeUpdatedAt,
186
+ },
187
+ rhythm: {
188
+ mode: latestRuntimeDecision?.mode ?? "unknown",
189
+ windowId: undefined,
190
+ },
191
+ quiet: {
192
+ mode: quietMode,
193
+ lastEvent: latestRuntimeDecision?.traceId,
194
+ interrupted: latestRuntimeDecision?.mode === "paused_for_interrupt"
195
+ ? true
196
+ : undefined,
197
+ },
198
+ connectors: connectorSummary,
199
+ credentials: credentials.map((item) => ({
200
+ platformId: item.platformId ??
201
+ item.platform_id,
202
+ status: item.status,
203
+ nextStep: buildCredentialNextStep(item.status),
204
+ })),
205
+ risk: {
206
+ level: riskFlags.length > 0 ? "medium" : "low",
207
+ flags: riskFlags,
208
+ },
209
+ // T1.2.5 (CH-14-04): default delivery posture is workspace_default_none because the
210
+ // workspace heartbeat hardcodes `deliveryCapability: { target: "none" }` until a host
211
+ // capability probe explicitly sets a valid target.
212
+ deliveryPosture: {
213
+ verdict: "none",
214
+ source: "workspace_default_none",
215
+ reasonCode: "delivery_target_none",
216
+ },
217
+ };
218
+ },
219
+ async loadDailyReport(day) {
220
+ let bundle;
221
+ try {
222
+ bundle = await quietLoader.loadQuietInputs({
223
+ dateRange: {
224
+ start: `${day}T00:00:00.000Z`,
225
+ end: `${day}T23:59:59.999Z`,
226
+ },
227
+ assetFilters: {
228
+ includeJournal: false,
229
+ includeReports: true,
230
+ includeCurated: false,
231
+ },
232
+ });
233
+ }
234
+ catch {
235
+ bundle = { dailyReports: [], journalEntries: [], sourceCount: 0 };
236
+ }
237
+ // T1.2.4: merge persisted Quiet artifact JSON files from `.second-nature/quiet/{day}/`
238
+ // into the daily report sourceRefs so the read model reflects artifacts written by
239
+ // `persistQuietArtifactToWorkspace` (closes the canonical read/write gap for loadDailyReport).
240
+ const fsArtifactCount = deps.workspaceRoot
241
+ ? countQuietArtifactsForDay(deps.workspaceRoot, day)
242
+ : 0;
243
+ const report = bundle.dailyReports[0];
244
+ const existingSources = report?.sources ?? [];
245
+ // Append synthetic source refs for each FS artifact not already in the list.
246
+ const fsSourceRefs = Array.from({ length: fsArtifactCount }, (_, i) => `quiet_artifact:${day}:${i}`).filter((ref) => !existingSources.includes(ref));
247
+ return {
248
+ day,
249
+ summary: report?.summary ?? "",
250
+ highlights: report?.highlights ?? [],
251
+ sourceRefs: [...existingSources, ...fsSourceRefs],
252
+ };
253
+ },
254
+ async loadQuiet(scope) {
255
+ const now = new Date();
256
+ const start = new Date(now);
257
+ start.setDate(now.getDate() - 1);
258
+ let bundle;
259
+ try {
260
+ bundle = await quietLoader.loadQuietInputs({
261
+ dateRange: { start: start.toISOString(), end: now.toISOString() },
262
+ });
263
+ }
264
+ catch {
265
+ bundle = { dailyReports: [], journalEntries: [], sourceCount: 0 };
266
+ }
267
+ // T1.2.4 (CH-14-07): also count persisted Quiet artifact JSON files under
268
+ // `.second-nature/quiet/` so that once `runSourceBackedQuiet` has written
269
+ // artifacts to disk, the read model is non-zero even if the legacy memory/
270
+ // journal path is empty.
271
+ const quietArtifacts = deps.workspaceRoot
272
+ ? countRecentQuietArtifacts(deps.workspaceRoot, 2)
273
+ : { totalArtifacts: 0, recentDays: [] };
274
+ const totalSourceCount = bundle.sourceCount + quietArtifacts.totalArtifacts;
275
+ const totalReportCount = bundle.dailyReports.length + quietArtifacts.totalArtifacts;
276
+ return {
277
+ scope,
278
+ mode: totalSourceCount > 0 ? "quiet" : "unknown",
279
+ sourceCount: totalSourceCount,
280
+ reportCount: totalReportCount,
281
+ recentJournalCount: bundle.journalEntries.length,
282
+ };
283
+ },
284
+ async loadSession(sessionId) {
285
+ const traceId = sessionId;
286
+ const bundle = await evidenceQuery.queryEvidence({ traceId });
287
+ return {
288
+ requestedSessionId: sessionId,
289
+ traceId,
290
+ decisionCount: bundle.decisions.length,
291
+ attemptCount: bundle.attempts.length,
292
+ governanceCount: bundle.governance.length,
293
+ keyFactors: bundle.explanation.keyFactors,
294
+ evidenceRefs: bundle.explanation.evidenceRefs,
295
+ };
296
+ },
297
+ async loadCredential(platformId) {
298
+ let record;
299
+ try {
300
+ record = await credentialRepository.findByPlatformId(platformId);
301
+ }
302
+ catch {
303
+ record = undefined;
304
+ }
305
+ if (!record) {
306
+ return {
307
+ platformId,
308
+ status: "missing",
309
+ nextStep: "provide_credential_context",
310
+ };
311
+ }
312
+ return {
313
+ platformId: record.platformId ??
314
+ record.platform_id,
315
+ status: record.status,
316
+ verificationDeadline: record.expiresAt ?? undefined,
317
+ attemptsRemaining: record.attemptsRemaining ?? undefined,
318
+ nextStep: buildCredentialNextStep(record.status),
319
+ };
320
+ },
321
+ async loadFallbackView(ref) {
322
+ const row = await loadOperatorFallbackRow(deps.stateDb, ref);
323
+ if (!row)
324
+ return null;
325
+ return toOperatorFallbackView(row);
326
+ },
327
+ async explain(subject) {
328
+ const q = toExplainQuery(subject);
329
+ // T1.2.5: auditStore is always non-null (default-injected), so the explain path always
330
+ // has a store available. For audit-only subjects with no matching events the summary
331
+ // from queryExplain will be "no_matching_audit_events" — accurate and non-alarming.
332
+ if (auditStore && q) {
333
+ const op = queryExplain(q, auditStore);
334
+ if (isAuditOnlySubjectKind(subject.kind)) {
335
+ return mapOperatorExplainToReadModel(op, subject.kind);
336
+ }
337
+ if (op.relatedEventIds.length > 0) {
338
+ return mapOperatorExplainToReadModel(op, subject.kind);
339
+ }
340
+ }
341
+ if (isAuditOnlySubjectKind(subject.kind)) {
342
+ return {
343
+ subjectType: subject.kind,
344
+ // auditStore is always present (default-injected by T1.2.5), so this branch is
345
+ // only reached when q is undefined (unresolvable subject kind).
346
+ conclusion: "no_matching_audit_events",
347
+ keyFactors: [],
348
+ evidenceRefs: [],
349
+ };
350
+ }
351
+ const query = subject.kind === "decision" ||
352
+ subject.kind === "platform-selection" ||
353
+ subject.kind === "outreach"
354
+ ? { decisionId: subject.id }
355
+ : { assetId: subject.id };
356
+ const bundle = await evidenceQuery.queryEvidence(query);
357
+ return {
358
+ subjectType: subject.kind,
359
+ conclusion: bundle.explanation.conclusion,
360
+ keyFactors: bundle.explanation.keyFactors,
361
+ evidenceRefs: bundle.explanation.evidenceRefs,
362
+ };
363
+ },
364
+ };
365
+ }