@haaaiawd/second-nature 0.1.24 → 0.1.26
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/index.js +78 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -5
- package/runtime/cli/commands/goal.d.ts +28 -0
- package/runtime/cli/commands/goal.js +163 -0
- package/runtime/cli/commands/index.js +38 -3
- package/runtime/cli/explain/resolve-subject.js +3 -0
- package/runtime/cli/ops/ops-router.d.ts +1 -1
- package/runtime/cli/ops/ops-router.js +63 -1
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +6 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +35 -1
- package/runtime/cli/read-models/index.d.ts +14 -2
- package/runtime/cli/read-models/index.js +403 -101
- package/runtime/cli/read-models/types.d.ts +90 -3
- package/runtime/core/second-nature/feedback/owner-reply-feedback.d.ts +46 -0
- package/runtime/core/second-nature/feedback/owner-reply-feedback.js +159 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +11 -1
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +78 -10
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +2 -0
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +1 -1
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +16 -2
- package/runtime/core/second-nature/index.d.ts +1 -0
- package/runtime/core/second-nature/index.js +1 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +16 -3
- package/runtime/core/second-nature/orchestrator/goal-priority.js +10 -9
- package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +29 -1
- package/runtime/core/second-nature/orchestrator/intent-planner.js +154 -79
- package/runtime/core/second-nature/orchestrator/narrative-update.js +23 -9
- package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +34 -0
- package/runtime/core/second-nature/orchestrator/platform-capability-router.js +115 -0
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +3 -1
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +39 -1
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +21 -2
- package/runtime/guidance/draft-outreach-message.js +14 -1
- package/runtime/guidance/outreach-draft-schema.d.ts +104 -0
- package/runtime/guidance/outreach-draft-schema.js +14 -0
- package/runtime/observability/audit/audit-envelope.d.ts +1 -1
- package/runtime/observability/query/explain-query.d.ts +3 -0
- package/runtime/observability/query/explain-query.js +9 -0
- package/runtime/observability/services/lived-experience-audit.d.ts +22 -0
- package/runtime/observability/services/lived-experience-audit.js +30 -0
- package/runtime/shared/types/credential.d.ts +1 -1
- package/runtime/storage/chronicle/session-chronicle-store.d.ts +1 -1
- package/runtime/storage/db/schema/narrative-state.d.ts +1 -1
- package/runtime/storage/db/schema/narrative-state.js +2 -2
- package/runtime/storage/services/credential-vault.d.ts +18 -0
- package/runtime/storage/services/credential-vault.js +73 -3
|
@@ -11,6 +11,9 @@ import { queryExplain, } from "../../observability/query/explain-query.js";
|
|
|
11
11
|
import { mapOperatorExplainToReadModel } from "./operator-explain-map.js";
|
|
12
12
|
import { loadOperatorFallbackRow, toOperatorFallbackView, } from "../../storage/fallback/load-operator-fallback.js";
|
|
13
13
|
import { loadRhythmPolicySnapshot, } from "../../storage/rhythm/rhythm-policy-snapshot.js";
|
|
14
|
+
import { createNarrativeStateStore } from "../../storage/narrative/narrative-state-store.js";
|
|
15
|
+
import { createRelationshipMemoryStore } from "../../storage/relationship/relationship-memory-store.js";
|
|
16
|
+
import { probeCredentialHealth } from "../../storage/services/credential-vault.js";
|
|
14
17
|
const INTERNAL_RUNTIME_PLATFORM_ID = "second-nature-runtime";
|
|
15
18
|
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
16
19
|
function toExplainQuery(subject) {
|
|
@@ -30,6 +33,8 @@ function toExplainQuery(subject) {
|
|
|
30
33
|
return { kind: "delivery", auditId: subject.id };
|
|
31
34
|
case "source_ref":
|
|
32
35
|
return { kind: "source_ref", sourceRefId: subject.id };
|
|
36
|
+
case "relationship":
|
|
37
|
+
return { kind: "relationship", relationshipId: subject.id };
|
|
33
38
|
default:
|
|
34
39
|
return undefined;
|
|
35
40
|
}
|
|
@@ -46,6 +51,8 @@ function buildCredentialNextStep(status) {
|
|
|
46
51
|
return "submit_verification_answer";
|
|
47
52
|
if (status === "expired" || status === "revoked" || status === "failed")
|
|
48
53
|
return "refresh_credential_context";
|
|
54
|
+
if (status === "decrypt_failed")
|
|
55
|
+
return "verify_or_re_create_credential_then_re_import";
|
|
49
56
|
return undefined;
|
|
50
57
|
}
|
|
51
58
|
/**
|
|
@@ -112,6 +119,133 @@ function mapConnectorStatus(attempt) {
|
|
|
112
119
|
}
|
|
113
120
|
return "healthy";
|
|
114
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Derive groundingStatus from confidence and status.
|
|
124
|
+
*
|
|
125
|
+
* Rules (in priority order):
|
|
126
|
+
* 1. blocked: status === "awaiting_sources" OR confidence < 0.4
|
|
127
|
+
* 2. pass: confidence >= 0.7 AND status === "active"
|
|
128
|
+
* 3. degraded: all other cases (0.4 <= confidence < 0.7, or status is insufficient_sources)
|
|
129
|
+
*/
|
|
130
|
+
function deriveGroundingStatus(status, confidence) {
|
|
131
|
+
if (status === "awaiting_sources" || confidence < 0.4) {
|
|
132
|
+
return "blocked";
|
|
133
|
+
}
|
|
134
|
+
if (confidence >= 0.7 && status === "active") {
|
|
135
|
+
return "pass";
|
|
136
|
+
}
|
|
137
|
+
return "degraded";
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build the base StatusReadModel that is shared by loadStatus and loadV6Status.
|
|
141
|
+
* Centralising this logic eliminates the DRY violation identified in CR-01.
|
|
142
|
+
*/
|
|
143
|
+
async function buildBaseStatus(deps) {
|
|
144
|
+
let recentAttempts = [];
|
|
145
|
+
let recentDecisions = [];
|
|
146
|
+
let credentials = [];
|
|
147
|
+
try {
|
|
148
|
+
recentAttempts = await deps.observabilityDb.db
|
|
149
|
+
.select()
|
|
150
|
+
.from(executionAttempts)
|
|
151
|
+
.orderBy(desc(executionAttempts.startedAt), desc(executionAttempts.finishedAt))
|
|
152
|
+
.limit(50);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
recentAttempts = [];
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
recentDecisions = await deps.observabilityDb.db
|
|
159
|
+
.select()
|
|
160
|
+
.from(decisionLedger)
|
|
161
|
+
.orderBy(desc(decisionLedger.createdAt))
|
|
162
|
+
.limit(50);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
recentDecisions = [];
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
credentials = await deps.stateDb.db.query.credentialRecords.findMany();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
credentials = [];
|
|
172
|
+
}
|
|
173
|
+
const latestRuntimeAttempt = recentAttempts.find((attempt) => attempt.platformId === INTERNAL_RUNTIME_PLATFORM_ID);
|
|
174
|
+
const latestConnectorAttempt = recentAttempts.find((attempt) => attempt.platformId !== INTERNAL_RUNTIME_PLATFORM_ID);
|
|
175
|
+
const latestRuntimeDecision = recentDecisions.find((decision) => decision.traceId.startsWith(INTERNAL_RUNTIME_TRACE_PREFIX));
|
|
176
|
+
const runtimeUpdatedAt = latestRuntimeAttempt?.finishedAt ??
|
|
177
|
+
latestRuntimeAttempt?.startedAt ??
|
|
178
|
+
latestRuntimeDecision?.createdAt ??
|
|
179
|
+
"";
|
|
180
|
+
const quietMode = latestRuntimeDecision?.mode === "quiet" ||
|
|
181
|
+
latestRuntimeDecision?.mode === "maintenance_only" ||
|
|
182
|
+
latestRuntimeDecision?.mode === "paused_for_interrupt"
|
|
183
|
+
? latestRuntimeDecision.mode
|
|
184
|
+
: "unknown";
|
|
185
|
+
const riskFlags = [
|
|
186
|
+
latestRuntimeAttempt?.failureClass,
|
|
187
|
+
latestConnectorAttempt?.failureClass,
|
|
188
|
+
].filter((value) => Boolean(value));
|
|
189
|
+
const connectorSummary = latestConnectorAttempt
|
|
190
|
+
? [
|
|
191
|
+
{
|
|
192
|
+
platformId: latestConnectorAttempt.platformId,
|
|
193
|
+
status: mapConnectorStatus(latestConnectorAttempt),
|
|
194
|
+
channel: latestConnectorAttempt.channel,
|
|
195
|
+
failureClass: latestConnectorAttempt.failureClass ?? undefined,
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
: [];
|
|
199
|
+
return {
|
|
200
|
+
runtime: {
|
|
201
|
+
host: "openclaw-plugin",
|
|
202
|
+
serviceStatus: mapRuntimeStatus(latestRuntimeAttempt),
|
|
203
|
+
updatedAt: runtimeUpdatedAt,
|
|
204
|
+
},
|
|
205
|
+
rhythm: {
|
|
206
|
+
mode: latestRuntimeDecision?.mode ?? "unknown",
|
|
207
|
+
},
|
|
208
|
+
quiet: {
|
|
209
|
+
mode: quietMode,
|
|
210
|
+
lastEvent: latestRuntimeDecision?.traceId,
|
|
211
|
+
interrupted: latestRuntimeDecision?.mode === "paused_for_interrupt"
|
|
212
|
+
? true
|
|
213
|
+
: undefined,
|
|
214
|
+
},
|
|
215
|
+
connectors: connectorSummary,
|
|
216
|
+
credentials: credentials.map((item) => {
|
|
217
|
+
const platformId = item.platformId ??
|
|
218
|
+
item.platform_id;
|
|
219
|
+
const encryptedValue = item.encryptedValue ??
|
|
220
|
+
item.encrypted_value;
|
|
221
|
+
const baseUrl = item.baseUrl ??
|
|
222
|
+
item.base_url;
|
|
223
|
+
const health = probeCredentialHealth(platformId, encryptedValue, baseUrl);
|
|
224
|
+
const effectiveStatus = health.state === "decrypt_failed"
|
|
225
|
+
? "decrypt_failed"
|
|
226
|
+
: item.status;
|
|
227
|
+
return {
|
|
228
|
+
platformId,
|
|
229
|
+
status: effectiveStatus,
|
|
230
|
+
nextStep: health.diagnosticCode === "missing_runtime_secret"
|
|
231
|
+
? "set_SECOND_NATURE_ENCRYPTION_KEY_then_re_probe"
|
|
232
|
+
: health.diagnosticCode === "credential_recovery_required"
|
|
233
|
+
? "verify_or_re_create_credential_then_re_import"
|
|
234
|
+
: buildCredentialNextStep(effectiveStatus),
|
|
235
|
+
keyHealth: health.keyHealth,
|
|
236
|
+
};
|
|
237
|
+
}),
|
|
238
|
+
risk: {
|
|
239
|
+
level: riskFlags.length > 0 ? "medium" : "low",
|
|
240
|
+
flags: riskFlags,
|
|
241
|
+
},
|
|
242
|
+
deliveryPosture: {
|
|
243
|
+
verdict: "none",
|
|
244
|
+
source: "workspace_default_none",
|
|
245
|
+
reasonCode: "delivery_target_none",
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
115
249
|
export function createCliReadModels(deps) {
|
|
116
250
|
const assetRepository = new AssetRepository(deps.stateDb);
|
|
117
251
|
const credentialRepository = new CredentialRepository(deps.stateDb);
|
|
@@ -124,103 +258,7 @@ export function createCliReadModels(deps) {
|
|
|
124
258
|
const auditStore = deps.livedExperienceAuditStore ?? new AppendOnlyAuditStore();
|
|
125
259
|
return {
|
|
126
260
|
async loadStatus(_scope) {
|
|
127
|
-
|
|
128
|
-
let recentDecisions = [];
|
|
129
|
-
let credentials = [];
|
|
130
|
-
try {
|
|
131
|
-
recentAttempts = await deps.observabilityDb.db
|
|
132
|
-
.select()
|
|
133
|
-
.from(executionAttempts)
|
|
134
|
-
.orderBy(desc(executionAttempts.startedAt), desc(executionAttempts.finishedAt))
|
|
135
|
-
.limit(50);
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
recentAttempts = [];
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
recentDecisions = await deps.observabilityDb.db
|
|
142
|
-
.select()
|
|
143
|
-
.from(decisionLedger)
|
|
144
|
-
.orderBy(desc(decisionLedger.createdAt))
|
|
145
|
-
.limit(50);
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
recentDecisions = [];
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
credentials = await deps.stateDb.db.query.credentialRecords.findMany();
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
credentials = [];
|
|
155
|
-
}
|
|
156
|
-
const latestRuntimeAttempt = recentAttempts.find((attempt) => attempt.platformId === INTERNAL_RUNTIME_PLATFORM_ID);
|
|
157
|
-
// CH-15-04 (CH-14-03): latestConnectorAttempt is the most recent execution attempt whose
|
|
158
|
-
// platformId is NOT the internal sn-runtime sentinel — i.e. a real connector platform
|
|
159
|
-
// (Moltbook, EvoMap, etc.). The `connectors` array in StatusReadModel reflects this single
|
|
160
|
-
// most-recent non-runtime attempt, NOT the full connector manifest. An empty array means
|
|
161
|
-
// no connector attempt has been recorded yet, not that connectors are misconfigured.
|
|
162
|
-
const latestConnectorAttempt = recentAttempts.find((attempt) => attempt.platformId !== INTERNAL_RUNTIME_PLATFORM_ID);
|
|
163
|
-
const latestRuntimeDecision = recentDecisions.find((decision) => decision.traceId.startsWith(INTERNAL_RUNTIME_TRACE_PREFIX));
|
|
164
|
-
const runtimeUpdatedAt = latestRuntimeAttempt?.finishedAt ??
|
|
165
|
-
latestRuntimeAttempt?.startedAt ??
|
|
166
|
-
latestRuntimeDecision?.createdAt ??
|
|
167
|
-
"";
|
|
168
|
-
const quietMode = latestRuntimeDecision?.mode === "quiet" ||
|
|
169
|
-
latestRuntimeDecision?.mode === "maintenance_only" ||
|
|
170
|
-
latestRuntimeDecision?.mode === "paused_for_interrupt"
|
|
171
|
-
? latestRuntimeDecision.mode
|
|
172
|
-
: "unknown";
|
|
173
|
-
const riskFlags = [
|
|
174
|
-
latestRuntimeAttempt?.failureClass,
|
|
175
|
-
latestConnectorAttempt?.failureClass,
|
|
176
|
-
].filter((value) => Boolean(value));
|
|
177
|
-
const connectorSummary = latestConnectorAttempt
|
|
178
|
-
? [
|
|
179
|
-
{
|
|
180
|
-
platformId: latestConnectorAttempt.platformId,
|
|
181
|
-
status: mapConnectorStatus(latestConnectorAttempt),
|
|
182
|
-
channel: latestConnectorAttempt.channel,
|
|
183
|
-
failureClass: latestConnectorAttempt.failureClass ?? undefined,
|
|
184
|
-
},
|
|
185
|
-
]
|
|
186
|
-
: [];
|
|
187
|
-
return {
|
|
188
|
-
runtime: {
|
|
189
|
-
host: "openclaw-plugin",
|
|
190
|
-
serviceStatus: mapRuntimeStatus(latestRuntimeAttempt),
|
|
191
|
-
updatedAt: runtimeUpdatedAt,
|
|
192
|
-
},
|
|
193
|
-
rhythm: {
|
|
194
|
-
mode: latestRuntimeDecision?.mode ?? "unknown",
|
|
195
|
-
windowId: undefined,
|
|
196
|
-
},
|
|
197
|
-
quiet: {
|
|
198
|
-
mode: quietMode,
|
|
199
|
-
lastEvent: latestRuntimeDecision?.traceId,
|
|
200
|
-
interrupted: latestRuntimeDecision?.mode === "paused_for_interrupt"
|
|
201
|
-
? true
|
|
202
|
-
: undefined,
|
|
203
|
-
},
|
|
204
|
-
connectors: connectorSummary,
|
|
205
|
-
credentials: credentials.map((item) => ({
|
|
206
|
-
platformId: item.platformId ??
|
|
207
|
-
item.platform_id,
|
|
208
|
-
status: item.status,
|
|
209
|
-
nextStep: buildCredentialNextStep(item.status),
|
|
210
|
-
})),
|
|
211
|
-
risk: {
|
|
212
|
-
level: riskFlags.length > 0 ? "medium" : "low",
|
|
213
|
-
flags: riskFlags,
|
|
214
|
-
},
|
|
215
|
-
// T1.2.5 (CH-14-04): default delivery posture is workspace_default_none because the
|
|
216
|
-
// workspace heartbeat hardcodes `deliveryCapability: { target: "none" }` until a host
|
|
217
|
-
// capability probe explicitly sets a valid target.
|
|
218
|
-
deliveryPosture: {
|
|
219
|
-
verdict: "none",
|
|
220
|
-
source: "workspace_default_none",
|
|
221
|
-
reasonCode: "delivery_target_none",
|
|
222
|
-
},
|
|
223
|
-
};
|
|
261
|
+
return buildBaseStatus(deps);
|
|
224
262
|
},
|
|
225
263
|
async loadDailyReport(day) {
|
|
226
264
|
let bundle;
|
|
@@ -309,19 +347,40 @@ export function createCliReadModels(deps) {
|
|
|
309
347
|
record = undefined;
|
|
310
348
|
}
|
|
311
349
|
if (!record) {
|
|
350
|
+
// T1.4.1: even when no row exists, probe key health so status can surface
|
|
351
|
+
// missing_runtime_secret rather than a generic "missing".
|
|
352
|
+
const health = probeCredentialHealth(platformId, null, null);
|
|
312
353
|
return {
|
|
313
354
|
platformId,
|
|
314
|
-
status:
|
|
315
|
-
nextStep: "
|
|
355
|
+
status: health.state,
|
|
356
|
+
nextStep: health.diagnosticCode === "missing_runtime_secret"
|
|
357
|
+
? "set_SECOND_NATURE_ENCRYPTION_KEY_then_re_probe"
|
|
358
|
+
: "provide_credential_context",
|
|
359
|
+
keyHealth: health.keyHealth,
|
|
316
360
|
};
|
|
317
361
|
}
|
|
362
|
+
// T1.4.1: attempt decryption to detect decrypt_failed / wrong_key.
|
|
363
|
+
const encryptedValue = record.encryptedValue ??
|
|
364
|
+
record.encrypted_value;
|
|
365
|
+
const baseUrl = record.baseUrl ??
|
|
366
|
+
record.base_url;
|
|
367
|
+
const health = probeCredentialHealth(platformId, encryptedValue, baseUrl);
|
|
368
|
+
// If decryption failed, surface the honest diagnostic; otherwise surface DB status.
|
|
369
|
+
const effectiveStatus = health.state === "decrypt_failed"
|
|
370
|
+
? "decrypt_failed"
|
|
371
|
+
: record.status;
|
|
318
372
|
return {
|
|
319
373
|
platformId: record.platformId ??
|
|
320
374
|
record.platform_id,
|
|
321
|
-
status:
|
|
375
|
+
status: effectiveStatus,
|
|
322
376
|
verificationDeadline: record.expiresAt ?? undefined,
|
|
323
377
|
attemptsRemaining: record.attemptsRemaining ?? undefined,
|
|
324
|
-
nextStep:
|
|
378
|
+
nextStep: health.diagnosticCode === "missing_runtime_secret"
|
|
379
|
+
? "set_SECOND_NATURE_ENCRYPTION_KEY_then_re_probe"
|
|
380
|
+
: health.diagnosticCode === "credential_recovery_required"
|
|
381
|
+
? "verify_or_re_create_credential_then_re_import"
|
|
382
|
+
: buildCredentialNextStep(effectiveStatus),
|
|
383
|
+
keyHealth: health.keyHealth,
|
|
325
384
|
};
|
|
326
385
|
},
|
|
327
386
|
async loadFallbackView(ref) {
|
|
@@ -374,6 +433,34 @@ export function createCliReadModels(deps) {
|
|
|
374
433
|
evidenceRefs: [],
|
|
375
434
|
};
|
|
376
435
|
}
|
|
436
|
+
// T1.4.2: relationship explain reads RelationshipMemory store directly.
|
|
437
|
+
if (subject.kind === "relationship") {
|
|
438
|
+
const relationshipStore = createRelationshipMemoryStore(deps.stateDb);
|
|
439
|
+
const memory = await relationshipStore.loadRelationshipMemory(subject.id);
|
|
440
|
+
if (!memory) {
|
|
441
|
+
return {
|
|
442
|
+
subjectType: "relationship",
|
|
443
|
+
conclusion: "nothing_yet",
|
|
444
|
+
keyFactors: ["no_relationship_memory_recorded"],
|
|
445
|
+
evidenceRefs: [],
|
|
446
|
+
nextStep: "interact_with_agent_then_re_check",
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
subjectType: "relationship",
|
|
451
|
+
conclusion: `tone:${memory.tonePreference} replies:${memory.noReplyCount === 0 ? "responsive" : "cooldown"}`,
|
|
452
|
+
keyFactors: [
|
|
453
|
+
`tone_preference:${memory.tonePreference}`,
|
|
454
|
+
...(memory.averageReplyDelayMinutes
|
|
455
|
+
? [`avg_reply_delay_minutes:${memory.averageReplyDelayMinutes}`]
|
|
456
|
+
: []),
|
|
457
|
+
...(memory.topicAffinities.length > 0
|
|
458
|
+
? [`topics:${memory.topicAffinities.map((t) => t.topic).join(",")}`]
|
|
459
|
+
: ["insufficient_history"]),
|
|
460
|
+
],
|
|
461
|
+
evidenceRefs: memory.sourceRefs.map((s) => s.sourceId),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
377
464
|
const query = subject.kind === "decision" ||
|
|
378
465
|
subject.kind === "platform-selection" ||
|
|
379
466
|
subject.kind === "outreach"
|
|
@@ -387,5 +474,220 @@ export function createCliReadModels(deps) {
|
|
|
387
474
|
evidenceRefs: bundle.explanation.evidenceRefs,
|
|
388
475
|
};
|
|
389
476
|
},
|
|
477
|
+
// T1.2.2 — read recent DreamTrace events from audit store.
|
|
478
|
+
async loadDreamRecent(limit = 5) {
|
|
479
|
+
const events = auditStore.list().filter((e) => e.family === "dream.trace");
|
|
480
|
+
const recent = events
|
|
481
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
482
|
+
.slice(0, limit);
|
|
483
|
+
return {
|
|
484
|
+
runs: recent.map((e) => {
|
|
485
|
+
const p = e.payload;
|
|
486
|
+
return {
|
|
487
|
+
traceId: p.traceId,
|
|
488
|
+
runId: p.runId,
|
|
489
|
+
durationMs: p.durationMs ?? 0,
|
|
490
|
+
inputCounts: p.inputCounts ?? { evidence: 0, chronicle: 0, memoryEntries: 0 },
|
|
491
|
+
fallbackReason: p.fallbackReason,
|
|
492
|
+
lifecycleStatus: p.fallbackReason ? "partial" : "completed",
|
|
493
|
+
insightsCount: 0, // would require deeper payload parsing
|
|
494
|
+
createdAt: e.createdAt,
|
|
495
|
+
};
|
|
496
|
+
}),
|
|
497
|
+
totalRuns: events.length,
|
|
498
|
+
};
|
|
499
|
+
},
|
|
500
|
+
// T1.2.5 — aggregate recent heartbeat, narrative, dream, delivery events into cycles.
|
|
501
|
+
async loadCycleRecent(limit = 5) {
|
|
502
|
+
const events = auditStore.list();
|
|
503
|
+
const decisions = events.filter((e) => e.family === "heartbeat.decision");
|
|
504
|
+
const narratives = events.filter((e) => e.family === "narrative.trace");
|
|
505
|
+
const dreams = events.filter((e) => e.family === "dream.trace");
|
|
506
|
+
const deliveries = events.filter((e) => e.family === "delivery");
|
|
507
|
+
const connectors = events.filter((e) => e.family === "connector.attempt");
|
|
508
|
+
// Group by time buckets (hourly)
|
|
509
|
+
const buckets = new Map();
|
|
510
|
+
for (const e of decisions) {
|
|
511
|
+
const hour = e.createdAt.slice(0, 13);
|
|
512
|
+
const b = buckets.get(hour) ?? { timestamp: `${hour}:00:00Z`, dimensions: [] };
|
|
513
|
+
if (!b.dimensions.includes("decision"))
|
|
514
|
+
b.dimensions.push("decision");
|
|
515
|
+
const p = e.payload;
|
|
516
|
+
if (p.outcome)
|
|
517
|
+
b.decisionOutcome = p.outcome;
|
|
518
|
+
buckets.set(hour, b);
|
|
519
|
+
}
|
|
520
|
+
for (const e of narratives) {
|
|
521
|
+
const hour = e.createdAt.slice(0, 13);
|
|
522
|
+
const b = buckets.get(hour) ?? { timestamp: `${hour}:00:00Z`, dimensions: [] };
|
|
523
|
+
if (!b.dimensions.includes("narrative"))
|
|
524
|
+
b.dimensions.push("narrative");
|
|
525
|
+
const p = e.payload;
|
|
526
|
+
if (p.groundingStatus)
|
|
527
|
+
b.narrativeGrounding = p.groundingStatus;
|
|
528
|
+
buckets.set(hour, b);
|
|
529
|
+
}
|
|
530
|
+
for (const e of dreams) {
|
|
531
|
+
const hour = e.createdAt.slice(0, 13);
|
|
532
|
+
const b = buckets.get(hour) ?? { timestamp: `${hour}:00:00Z`, dimensions: [] };
|
|
533
|
+
if (!b.dimensions.includes("dream"))
|
|
534
|
+
b.dimensions.push("dream");
|
|
535
|
+
const p = e.payload;
|
|
536
|
+
if (p.fallbackReason)
|
|
537
|
+
b.dreamFallback = p.fallbackReason;
|
|
538
|
+
buckets.set(hour, b);
|
|
539
|
+
}
|
|
540
|
+
for (const e of deliveries) {
|
|
541
|
+
const hour = e.createdAt.slice(0, 13);
|
|
542
|
+
const b = buckets.get(hour) ?? { timestamp: `${hour}:00:00Z`, dimensions: [] };
|
|
543
|
+
if (!b.dimensions.includes("delivery"))
|
|
544
|
+
b.dimensions.push("delivery");
|
|
545
|
+
const p = e.payload;
|
|
546
|
+
if (p.status)
|
|
547
|
+
b.deliveryStatus = p.status;
|
|
548
|
+
buckets.set(hour, b);
|
|
549
|
+
}
|
|
550
|
+
for (const e of connectors) {
|
|
551
|
+
const hour = e.createdAt.slice(0, 13);
|
|
552
|
+
const b = buckets.get(hour) ?? { timestamp: `${hour}:00:00Z`, dimensions: [] };
|
|
553
|
+
if (!b.dimensions.includes("connector"))
|
|
554
|
+
b.dimensions.push("connector");
|
|
555
|
+
buckets.set(hour, b);
|
|
556
|
+
}
|
|
557
|
+
const cycles = Array.from(buckets.values())
|
|
558
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
559
|
+
.slice(0, limit);
|
|
560
|
+
return { cycles, totalCycles: buckets.size };
|
|
561
|
+
},
|
|
562
|
+
// T1.2.6 — v6 status aggregate: compose base status + narrative + dream + cycle sections.
|
|
563
|
+
// Each section returns a sentinel status (nothing_yet / has_runs / has_cycles) so operators
|
|
564
|
+
// always get a meaningful non-empty response, never a raw empty object.
|
|
565
|
+
async loadV6Status(scope) {
|
|
566
|
+
// Load NarrativeState asynchronously; audit events are synchronous reads from in-memory store.
|
|
567
|
+
const narrativeStore = createNarrativeStateStore(deps.stateDb);
|
|
568
|
+
let narrativeState;
|
|
569
|
+
try {
|
|
570
|
+
narrativeState = await narrativeStore.loadNarrativeState();
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
narrativeState = null;
|
|
574
|
+
}
|
|
575
|
+
const allAuditEvents = auditStore.list();
|
|
576
|
+
const dreamSection = allAuditEvents.filter((e) => e.family === "dream.trace");
|
|
577
|
+
const cycleSection = allAuditEvents;
|
|
578
|
+
const baseStatus = await buildBaseStatus(deps);
|
|
579
|
+
// Narrative section
|
|
580
|
+
let narrativeSectionOut;
|
|
581
|
+
if (!narrativeState) {
|
|
582
|
+
narrativeSectionOut = { status: "nothing_yet", focus: "", groundingStatus: "blocked", nextIntent: "", sourceRefCount: 0 };
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
const groundingStatus = deriveGroundingStatus(narrativeState.status, narrativeState.confidence);
|
|
586
|
+
narrativeSectionOut = { status: narrativeState.status, focus: narrativeState.focus, groundingStatus, nextIntent: narrativeState.nextIntent, sourceRefCount: narrativeState.sourceRefs.length };
|
|
587
|
+
}
|
|
588
|
+
// Dream section — degraded when all recorded dream runs have a fallbackReason.
|
|
589
|
+
const dreamEvents = dreamSection;
|
|
590
|
+
let dreamSectionOut;
|
|
591
|
+
if (dreamEvents.length === 0) {
|
|
592
|
+
dreamSectionOut = { status: "nothing_yet", totalRuns: 0, recentRunCount: 0 };
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const recentDreams = dreamEvents.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 3);
|
|
596
|
+
const lastFallback = recentDreams.map((e) => e.payload.fallbackReason).find(Boolean);
|
|
597
|
+
const allDegraded = dreamEvents.every((e) => !!e.payload.fallbackReason);
|
|
598
|
+
dreamSectionOut = {
|
|
599
|
+
status: allDegraded ? "degraded" : "has_runs",
|
|
600
|
+
totalRuns: dreamEvents.length,
|
|
601
|
+
recentRunCount: recentDreams.length,
|
|
602
|
+
lastFallbackReason: lastFallback,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
// Cycle section — degraded when buckets exist but cover fewer than 3 dimensions.
|
|
606
|
+
const allEvents = cycleSection;
|
|
607
|
+
const decisionEvents = allEvents.filter((e) => e.family === "heartbeat.decision");
|
|
608
|
+
const narrativeEvents = allEvents.filter((e) => e.family === "narrative.trace");
|
|
609
|
+
const dreamEventsForCycle = allEvents.filter((e) => e.family === "dream.trace");
|
|
610
|
+
const deliveryEvents = allEvents.filter((e) => e.family === "delivery");
|
|
611
|
+
const connectorEvents = allEvents.filter((e) => e.family === "connector.attempt");
|
|
612
|
+
const hourBuckets = new Set();
|
|
613
|
+
const dimensionSet = new Set();
|
|
614
|
+
for (const e of decisionEvents) {
|
|
615
|
+
hourBuckets.add(e.createdAt.slice(0, 13));
|
|
616
|
+
dimensionSet.add("decision");
|
|
617
|
+
}
|
|
618
|
+
for (const e of narrativeEvents) {
|
|
619
|
+
hourBuckets.add(e.createdAt.slice(0, 13));
|
|
620
|
+
dimensionSet.add("narrative");
|
|
621
|
+
}
|
|
622
|
+
for (const e of dreamEventsForCycle) {
|
|
623
|
+
hourBuckets.add(e.createdAt.slice(0, 13));
|
|
624
|
+
dimensionSet.add("dream");
|
|
625
|
+
}
|
|
626
|
+
for (const e of deliveryEvents) {
|
|
627
|
+
hourBuckets.add(e.createdAt.slice(0, 13));
|
|
628
|
+
dimensionSet.add("delivery");
|
|
629
|
+
}
|
|
630
|
+
for (const e of connectorEvents) {
|
|
631
|
+
hourBuckets.add(e.createdAt.slice(0, 13));
|
|
632
|
+
dimensionSet.add("connector");
|
|
633
|
+
}
|
|
634
|
+
let cycleSectionOut;
|
|
635
|
+
if (hourBuckets.size === 0) {
|
|
636
|
+
cycleSectionOut = { status: "nothing_yet", totalCycles: 0, recentCycleCount: 0, dimensions: [] };
|
|
637
|
+
}
|
|
638
|
+
else if (dimensionSet.size < 3) {
|
|
639
|
+
cycleSectionOut = { status: "degraded", totalCycles: hourBuckets.size, recentCycleCount: Math.min(hourBuckets.size, 5), dimensions: Array.from(dimensionSet) };
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
cycleSectionOut = { status: "has_cycles", totalCycles: hourBuckets.size, recentCycleCount: Math.min(hourBuckets.size, 5), dimensions: Array.from(dimensionSet) };
|
|
643
|
+
}
|
|
644
|
+
void scope; // scope param reserved for future scoping — not used in v6 aggregate yet
|
|
645
|
+
return { ...baseStatus, narrative: narrativeSectionOut, dream: dreamSectionOut, cycles: cycleSectionOut };
|
|
646
|
+
},
|
|
647
|
+
// T1.2.1 — read current NarrativeState and map to NarrativeReadModel.
|
|
648
|
+
// Returns `nothing_yet` status when no data exists — honest empty, not an error.
|
|
649
|
+
async loadNarrative(narrativeId) {
|
|
650
|
+
const narrativeStore = createNarrativeStateStore(deps.stateDb);
|
|
651
|
+
let state;
|
|
652
|
+
try {
|
|
653
|
+
state = await narrativeStore.loadNarrativeState(narrativeId);
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
state = null;
|
|
657
|
+
}
|
|
658
|
+
if (!state) {
|
|
659
|
+
return {
|
|
660
|
+
narrativeId: narrativeId ?? "default",
|
|
661
|
+
revision: 0,
|
|
662
|
+
focus: "",
|
|
663
|
+
progress: [],
|
|
664
|
+
nextIntent: "",
|
|
665
|
+
confidence: 0,
|
|
666
|
+
sourceRefs: [],
|
|
667
|
+
unsupportedClaims: [],
|
|
668
|
+
groundingStatus: "blocked",
|
|
669
|
+
status: "nothing_yet",
|
|
670
|
+
updatedAt: "",
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
const groundingStatus = deriveGroundingStatus(state.status, state.confidence);
|
|
674
|
+
return {
|
|
675
|
+
narrativeId: state.narrativeId,
|
|
676
|
+
revision: state.revision,
|
|
677
|
+
focus: state.focus,
|
|
678
|
+
progress: state.progress,
|
|
679
|
+
nextIntent: state.nextIntent,
|
|
680
|
+
confidence: state.confidence,
|
|
681
|
+
sourceRefs: state.sourceRefs.map((r) => ({
|
|
682
|
+
sourceId: r.sourceId,
|
|
683
|
+
kind: r.kind,
|
|
684
|
+
url: r.url,
|
|
685
|
+
})),
|
|
686
|
+
unsupportedClaims: state.unsupportedClaims,
|
|
687
|
+
groundingStatus,
|
|
688
|
+
status: state.status,
|
|
689
|
+
updatedAt: state.updatedAt,
|
|
690
|
+
};
|
|
691
|
+
},
|
|
390
692
|
};
|
|
391
693
|
}
|
|
@@ -25,8 +25,10 @@ export interface ConnectorSummary {
|
|
|
25
25
|
}
|
|
26
26
|
export interface CredentialSummary {
|
|
27
27
|
platformId: string;
|
|
28
|
-
status: "missing" | "pending_verification" | "active" | "expired" | "revoked" | "failed";
|
|
28
|
+
status: "missing" | "pending_verification" | "active" | "expired" | "revoked" | "failed" | "decrypt_failed";
|
|
29
29
|
nextStep?: string;
|
|
30
|
+
/** T1.4.1 — diagnostic key health without leaking raw secret. */
|
|
31
|
+
keyHealth?: "missing_key" | "wrong_key" | "ok";
|
|
30
32
|
}
|
|
31
33
|
export interface RiskSummary {
|
|
32
34
|
level: "low" | "medium" | "high";
|
|
@@ -73,6 +75,38 @@ export interface StatusReadModel {
|
|
|
73
75
|
*/
|
|
74
76
|
deliveryPosture?: DeliveryPosture;
|
|
75
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* T1.2.6 — v6 status aggregate: extends StatusReadModel with narrative, dream recent,
|
|
80
|
+
* cycle recent, and per-section `nothing_yet` / `awaiting_sources` / `degraded` sentinels
|
|
81
|
+
* so operators always get a meaningful view even when individual data sources are empty.
|
|
82
|
+
*/
|
|
83
|
+
export interface StatusV6NarrativeSection {
|
|
84
|
+
status: "active" | "insufficient_sources" | "awaiting_sources" | "nothing_yet";
|
|
85
|
+
focus: string;
|
|
86
|
+
groundingStatus: "pass" | "degraded" | "blocked";
|
|
87
|
+
nextIntent: string;
|
|
88
|
+
sourceRefCount: number;
|
|
89
|
+
}
|
|
90
|
+
export interface StatusV6DreamSection {
|
|
91
|
+
status: "has_runs" | "degraded" | "nothing_yet";
|
|
92
|
+
totalRuns: number;
|
|
93
|
+
recentRunCount: number;
|
|
94
|
+
lastFallbackReason?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface StatusV6CycleSection {
|
|
97
|
+
status: "has_cycles" | "degraded" | "nothing_yet";
|
|
98
|
+
totalCycles: number;
|
|
99
|
+
recentCycleCount: number;
|
|
100
|
+
dimensions: string[];
|
|
101
|
+
}
|
|
102
|
+
export interface StatusV6ReadModel extends StatusReadModel {
|
|
103
|
+
/** v6 narrative section; status is nothing_yet when no NarrativeState row exists. */
|
|
104
|
+
narrative: StatusV6NarrativeSection;
|
|
105
|
+
/** v6 dream recent section; status is nothing_yet when no DreamTrace events exist. */
|
|
106
|
+
dream: StatusV6DreamSection;
|
|
107
|
+
/** v6 cycle recent section; status is nothing_yet when no cycle events exist. */
|
|
108
|
+
cycles: StatusV6CycleSection;
|
|
109
|
+
}
|
|
76
110
|
export interface DailyReportReadModel {
|
|
77
111
|
day: string;
|
|
78
112
|
summary: string;
|
|
@@ -97,12 +131,17 @@ export interface SessionDetailReadModel {
|
|
|
97
131
|
}
|
|
98
132
|
export interface CredentialReadModel {
|
|
99
133
|
platformId: string;
|
|
100
|
-
status: "missing" | "pending_verification" | "active" | "expired" | "revoked" | "failed";
|
|
134
|
+
status: "missing" | "pending_verification" | "active" | "expired" | "revoked" | "failed" | "decrypt_failed";
|
|
101
135
|
verificationDeadline?: string;
|
|
102
136
|
attemptsRemaining?: number;
|
|
103
137
|
nextStep?: string;
|
|
138
|
+
/**
|
|
139
|
+
* T1.4.1 — redacted diagnostic: when true, the raw encrypted value could not be
|
|
140
|
+
* decrypted because SECOND_NATURE_ENCRYPTION_KEY is missing or wrong.
|
|
141
|
+
*/
|
|
142
|
+
keyHealth?: "missing_key" | "wrong_key" | "ok";
|
|
104
143
|
}
|
|
105
|
-
export type ExplainSubjectKind = "decision" | "platform-selection" | "outreach" | "soul-change" | "fallback" | "probe" | "delivery" | "report" | "source_ref";
|
|
144
|
+
export type ExplainSubjectKind = "decision" | "platform-selection" | "outreach" | "soul-change" | "fallback" | "probe" | "delivery" | "report" | "source_ref" | "relationship";
|
|
106
145
|
export interface ExplainReadModel {
|
|
107
146
|
subjectType: ExplainSubjectKind;
|
|
108
147
|
conclusion: string;
|
|
@@ -127,3 +166,51 @@ export interface AuditSummaryReadModel {
|
|
|
127
166
|
totalEvents: number;
|
|
128
167
|
events: AuditEventSummaryEntry[];
|
|
129
168
|
}
|
|
169
|
+
/** T1.2.2 — recent Dream run summary for operator `dream:recent` command. */
|
|
170
|
+
export interface DreamRecentReadModel {
|
|
171
|
+
runs: Array<{
|
|
172
|
+
traceId: string;
|
|
173
|
+
runId: string;
|
|
174
|
+
durationMs: number;
|
|
175
|
+
inputCounts: {
|
|
176
|
+
evidence: number;
|
|
177
|
+
chronicle: number;
|
|
178
|
+
memoryEntries: number;
|
|
179
|
+
};
|
|
180
|
+
fallbackReason?: string;
|
|
181
|
+
lifecycleStatus: string;
|
|
182
|
+
insightsCount: number;
|
|
183
|
+
createdAt: string;
|
|
184
|
+
}>;
|
|
185
|
+
totalRuns: number;
|
|
186
|
+
}
|
|
187
|
+
/** T1.2.1 — NarrativeState read model for operator `narrative` command. */
|
|
188
|
+
export interface NarrativeReadModel {
|
|
189
|
+
narrativeId: string;
|
|
190
|
+
revision: number;
|
|
191
|
+
focus: string;
|
|
192
|
+
progress: string[];
|
|
193
|
+
nextIntent: string;
|
|
194
|
+
confidence: number;
|
|
195
|
+
sourceRefs: Array<{
|
|
196
|
+
sourceId: string;
|
|
197
|
+
kind: string;
|
|
198
|
+
url?: string;
|
|
199
|
+
}>;
|
|
200
|
+
unsupportedClaims: string[];
|
|
201
|
+
groundingStatus: "pass" | "degraded" | "blocked";
|
|
202
|
+
status: "active" | "insufficient_sources" | "awaiting_sources" | "nothing_yet";
|
|
203
|
+
updatedAt: string;
|
|
204
|
+
}
|
|
205
|
+
/** T1.2.5 — recent cycle summary aggregating heartbeat, narrative, dream, delivery. */
|
|
206
|
+
export interface CycleRecentReadModel {
|
|
207
|
+
cycles: Array<{
|
|
208
|
+
timestamp: string;
|
|
209
|
+
dimensions: Array<"decision" | "narrative" | "dream" | "delivery" | "connector">;
|
|
210
|
+
decisionOutcome?: string;
|
|
211
|
+
narrativeGrounding?: string;
|
|
212
|
+
dreamFallback?: string;
|
|
213
|
+
deliveryStatus?: string;
|
|
214
|
+
}>;
|
|
215
|
+
totalCycles: number;
|
|
216
|
+
}
|