@haaaiawd/second-nature 0.2.8 → 0.2.12

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 (90) hide show
  1. package/index.js +12 -3
  2. package/openclaw.plugin.json +3 -6
  3. package/package.json +1 -1
  4. package/runtime/cli/host-capability/probe-host-capability.js +1 -1
  5. package/runtime/cli/host-capability/types.d.ts +2 -7
  6. package/runtime/cli/host-capability/types.js +0 -5
  7. package/runtime/cli/ops/heartbeat-surface.d.ts +2 -0
  8. package/runtime/cli/ops/heartbeat-surface.js +32 -3
  9. package/runtime/cli/ops/manual-run-dispatcher.d.ts +10 -0
  10. package/runtime/cli/ops/manual-run-dispatcher.js +49 -0
  11. package/runtime/cli/ops/ops-router.js +5 -3
  12. package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
  13. package/runtime/connectors/base/normalized-evidence-content.js +15 -2
  14. package/runtime/connectors/manifest/manifest-schema.d.ts +2 -2
  15. package/runtime/connectors/registry/dynamic-connector-registry.js +16 -1
  16. package/runtime/connectors/services/connector-executor-adapter.js +58 -35
  17. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +2 -0
  18. package/runtime/core/second-nature/action/action-closure-recorder.js +2 -4
  19. package/runtime/core/second-nature/action/action-proposal-builder.js +5 -32
  20. package/runtime/core/second-nature/action/policy-bound-dispatch.js +4 -3
  21. package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +1 -11
  22. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +1 -0
  23. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +10 -6
  24. package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
  25. package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -0
  26. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +4 -3
  27. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +3 -2
  28. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -2
  29. package/runtime/core/second-nature/orchestrator/intent-planner.js +4 -2
  30. package/runtime/core/second-nature/orchestrator/narrative-update.js +1 -2
  31. package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +2 -2
  32. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +1 -1
  33. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +2 -3
  34. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +2 -2
  35. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +6 -1
  36. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +3 -14
  37. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +6 -5
  38. package/runtime/core/second-nature/perception/judgment-engine.js +2 -12
  39. package/runtime/core/second-nature/perception/perception-builder.js +1 -9
  40. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +13 -15
  41. package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +10 -13
  42. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +1 -0
  43. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +11 -19
  44. package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +0 -2
  45. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +0 -12
  46. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +1 -11
  47. package/runtime/core/second-nature/types.d.ts +2 -9
  48. package/runtime/dream/dream-engine.js +11 -4
  49. package/runtime/guidance/outreach-draft-schema.d.ts +12 -12
  50. package/runtime/guidance/persona-selection.js +5 -0
  51. package/runtime/guidance/template-registry.js +6 -1
  52. package/runtime/guidance/types.d.ts +2 -2
  53. package/runtime/observability/living-loop-health-gate.js +2 -8
  54. package/runtime/observability/loop-stage-event-sink.js +0 -1
  55. package/runtime/observability/services/lived-experience-audit.d.ts +7 -7
  56. package/runtime/shared/serialization.d.ts +17 -0
  57. package/runtime/shared/serialization.js +27 -0
  58. package/runtime/shared/source-ref-compat.d.ts +26 -0
  59. package/runtime/shared/source-ref-compat.js +61 -0
  60. package/runtime/shared/types/goal.d.ts +4 -4
  61. package/runtime/shared/types/goal.js +1 -1
  62. package/runtime/shared/types/index.d.ts +1 -0
  63. package/runtime/shared/types/index.js +1 -3
  64. package/runtime/shared/types/source-ref.d.ts +2 -2
  65. package/runtime/shared/types/v7-entities.d.ts +5 -5
  66. package/runtime/shared/types/v7-entities.js +1 -1
  67. package/runtime/shared/types/v8-contracts.d.ts +1 -1
  68. package/runtime/storage/db/index.js +31 -26
  69. package/runtime/storage/db/migrations/index.js +4 -0
  70. package/runtime/storage/db/migrations/v8-004-schema-closure.d.ts +19 -0
  71. package/runtime/storage/db/migrations/v8-004-schema-closure.js +74 -0
  72. package/runtime/storage/db/migrations/v8-005-single-status-schema.d.ts +11 -0
  73. package/runtime/storage/db/migrations/v8-005-single-status-schema.js +16 -0
  74. package/runtime/storage/db/schema/v8-entities.d.ts +0 -95
  75. package/runtime/storage/db/schema/v8-entities.js +4 -7
  76. package/runtime/storage/delivery/types.d.ts +2 -2
  77. package/runtime/storage/fallback/load-operator-fallback.d.ts +2 -2
  78. package/runtime/storage/fallback/operator-fallback-types.d.ts +2 -2
  79. package/runtime/storage/fallback/operator-fallback-view.d.ts +2 -2
  80. package/runtime/storage/index.d.ts +1 -1
  81. package/runtime/storage/life-evidence/types.d.ts +5 -5
  82. package/runtime/storage/quiet/quiet-artifact-types.d.ts +4 -4
  83. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +2 -2
  84. package/runtime/storage/services/write-validation-gate.d.ts +1 -1
  85. package/runtime/storage/services/write-validation-gate.js +16 -4
  86. package/runtime/storage/snapshots/types.d.ts +8 -8
  87. package/runtime/storage/user-interest/types.d.ts +3 -3
  88. package/runtime/storage/v8-state-stores.d.ts +8 -1
  89. package/runtime/storage/v8-state-stores.js +23 -20
  90. package/workspace-ops-bridge.js +29 -1
@@ -22,7 +22,7 @@
22
22
  * so `dreamStatus` reaches completed/blocked.
23
23
  * - Does not bypass Dream runner; only records due/completed/blocked.
24
24
  */
25
- import { writeDailyRhythmState, readDailyRhythmStateByDay, readActionClosuresByDay, readDreamConsolidationRunById, readDreamConsolidationRunsByQuietId, updateDreamConsolidationRunStatus, } from "../../../storage/v8-state-stores.js";
25
+ import { writeDailyRhythmState, readDailyRhythmStateByDay, readActionClosuresByDay, readDreamConsolidationRunById, readDreamConsolidationRunsByQuietId, readLatestDreamConsolidationRunByStatus, updateDreamConsolidationRunStatus, } from "../../../storage/v8-state-stores.js";
26
26
  import { buildQuietDailyReview } from "./quiet-daily-review-builder.js";
27
27
  import { scheduleDreamAfterQuiet } from "./dream-scheduler.js";
28
28
  import { runDreamConsolidation } from "./dream-consolidation-runner.js";
@@ -112,7 +112,6 @@ async function executeStaleScheduledDreams(db, state, now) {
112
112
  const finalReason = dreamResult.reason ?? undefined;
113
113
  const updateResult = await updateDreamConsolidationRunStatus(db, runId, finalStatus, {
114
114
  reason: finalReason ?? null,
115
- lifecycleStatus: finalStatus === "completed" ? "completed" : "archived",
116
115
  payloadJson: JSON.stringify({
117
116
  ...parsePayloadJson(run.payloadJson),
118
117
  consolidatedAt: now,
@@ -226,18 +225,17 @@ export async function checkDailyRhythm(db, options) {
226
225
  // Already handled; do not re-schedule
227
226
  }
228
227
  else {
228
+ // Global 7-day interval check: look across all quiet reviews, not just today's.
229
229
  const quietId = `quiet_${day}`;
230
- const latestRun = await loadLatestDreamRunForQuiet(db, quietId);
231
- if (latestRun.degraded) {
232
- return latestRun.degraded;
230
+ const globalLatest = await readLatestDreamConsolidationRunByStatus(db, ["completed", "blocked"]);
231
+ if (globalLatest.degraded) {
232
+ return globalLatest.degraded;
233
233
  }
234
- // If a completed/blocked run already exists within the 7-day interval, honor it.
235
- if (latestRun.row &&
236
- (latestRun.row.status === "completed" || latestRun.row.status === "blocked") &&
237
- isWithinDays(latestRun.row.createdAt, now, DREAM_DEFAULT_INTERVAL_DAYS)) {
238
- state.dreamStatus = latestRun.row.status;
239
- state.dreamReason = latestRun.row.reason ?? "dream_completed";
240
- if (latestRun.row.status === "completed") {
234
+ if (globalLatest.row &&
235
+ isWithinDays(globalLatest.row.createdAt, now, DREAM_DEFAULT_INTERVAL_DAYS)) {
236
+ state.dreamStatus = globalLatest.row.status;
237
+ state.dreamReason = "dream_interval_active";
238
+ if (globalLatest.row.status === "completed") {
241
239
  state.dreamCompletedAt = now;
242
240
  }
243
241
  }
@@ -268,7 +266,6 @@ export async function checkDailyRhythm(db, options) {
268
266
  const dreamOutcome = consolidateResult;
269
267
  const updateResult = await updateDreamConsolidationRunStatus(db, dreamResult.id, dreamOutcome.status, {
270
268
  reason: dreamOutcome.reason ?? null,
271
- lifecycleStatus: dreamOutcome.status === "completed" ? "completed" : "archived",
272
269
  payloadJson: JSON.stringify({
273
270
  consolidatedAt: now,
274
271
  candidateCount: dreamOutcome.candidates.length,
@@ -31,6 +31,7 @@ export interface DreamMemoryCandidate {
31
31
  confidence: number;
32
32
  validationStatus: "valid" | "rejected" | "blocked";
33
33
  validationReason?: string;
34
+ acceptedProjectionId?: string;
34
35
  }
35
36
  export interface RunDreamConsolidationResult {
36
37
  runId: string;
@@ -20,7 +20,8 @@
20
20
  *
21
21
  * Test coverage: tests/unit/dream/dream-consolidation-runner.test.ts
22
22
  */
23
- import { readDreamConsolidationRunById, readQuietDailyReviewById, writeLongTermMemoryProjection, } from "../../../storage/v8-state-stores.js";
23
+ import { readDreamConsolidationRunById, readQuietDailyReviewById, } from "../../../storage/v8-state-stores.js";
24
+ import { acceptMemoryProjection } from "./memory-projection-lifecycle.js";
24
25
  // ───────────────────────────────────────────────────────────────
25
26
  // Helpers
26
27
  // ───────────────────────────────────────────────────────────────
@@ -144,30 +145,21 @@ export async function runDreamConsolidation(db, runId, options) {
144
145
  reason: "dream_blocked_redaction",
145
146
  };
146
147
  }
147
- // Write valid candidates as projections (candidate status)
148
+ // Accept valid candidates as active long-term memory projections.
149
+ // This completes the Dream→memory lifecycle so accepted projections can be
150
+ // loaded by EmbodiedContext in subsequent heartbeats (T-DQ.R.3 followup).
148
151
  const validCandidates = candidates.filter((c) => c.validationStatus === "valid");
149
152
  for (const candidate of validCandidates) {
150
- const projectionResult = await writeLongTermMemoryProjection(db, {
151
- id: `proj_${candidate.id}`,
152
- createdAt: now,
153
- candidateId: candidate.id,
154
- topicKey: `topic_${review.day}`,
155
- status: "candidate",
156
- sourceRefs: candidate.sourceRefs,
157
- redactionClass: "none",
158
- lifecycleStatus: "candidate",
159
- payloadJson: JSON.stringify({
160
- candidateText: candidate.candidateText,
161
- confidence: candidate.confidence,
162
- runId,
163
- }),
164
- });
165
- if ("reason" in projectionResult) {
153
+ const acceptResult = await acceptMemoryProjection(db, candidate.id, `topic_${review.day}`, candidate.candidateText, candidate.sourceRefs, { now });
154
+ if ("projectionId" in acceptResult) {
155
+ candidate.acceptedProjectionId = acceptResult.projectionId;
156
+ }
157
+ else {
166
158
  return {
167
159
  runId,
168
160
  status: "failed",
169
161
  candidates,
170
- reason: projectionResult.reason,
162
+ reason: acceptResult.reason,
171
163
  };
172
164
  }
173
165
  }
@@ -60,7 +60,6 @@ export async function scheduleDreamAfterQuiet(db, quietReviewId, options) {
60
60
  },
61
61
  ],
62
62
  redactionClass: "none",
63
- lifecycleStatus: "pending",
64
63
  payloadJson: JSON.stringify({ scheduledAt: now, blocked: true }),
65
64
  });
66
65
  if ("reason" in writeResult) {
@@ -90,7 +89,6 @@ export async function scheduleDreamAfterQuiet(db, quietReviewId, options) {
90
89
  },
91
90
  ],
92
91
  redactionClass: "none",
93
- lifecycleStatus: "pending",
94
92
  payloadJson: JSON.stringify({ scheduledAt: now }),
95
93
  });
96
94
  if ("reason" in writeResult) {
@@ -65,7 +65,6 @@ export async function acceptMemoryProjection(db, candidateId, topicKey, memoryTe
65
65
  status: "active",
66
66
  sourceRefs,
67
67
  redactionClass: "none",
68
- lifecycleStatus: "active",
69
68
  payloadJson: JSON.stringify({
70
69
  memoryText,
71
70
  acceptedAt: now,
@@ -111,17 +110,6 @@ export async function retireMemoryProjection(db, projectionId, _candidateId, _to
111
110
  // ───────────────────────────────────────────────────────────────
112
111
  // Helpers
113
112
  // ───────────────────────────────────────────────────────────────
114
- function parseSourceRefs(json) {
115
- if (!json)
116
- return [];
117
- try {
118
- const parsed = JSON.parse(json);
119
- return Array.isArray(parsed) ? parsed : [];
120
- }
121
- catch {
122
- return [];
123
- }
124
- }
125
113
  function parsePayloadJson(json) {
126
114
  if (!json)
127
115
  return {};
@@ -22,6 +22,7 @@
22
22
  * Test coverage: tests/unit/quiet/quiet-daily-review-builder.test.ts
23
23
  */
24
24
  import { readActionClosuresByDay, writeQuietDailyReview, readPerceptionCardById, readEvidenceItemsByDay, readPerceptionCardsByDay, } from "../../../storage/v8-state-stores.js";
25
+ import { parseSourceRefs } from "../../../shared/serialization.js";
25
26
  // ───────────────────────────────────────────────────────────────
26
27
  // Config
27
28
  // ───────────────────────────────────────────────────────────────
@@ -52,17 +53,6 @@ function buildSourceRefFromClosure(closure) {
52
53
  resolveStatus: "resolvable",
53
54
  };
54
55
  }
55
- function parseSourceRefs(json) {
56
- if (!json)
57
- return [];
58
- try {
59
- const parsed = JSON.parse(json);
60
- return Array.isArray(parsed) ? parsed : [];
61
- }
62
- catch {
63
- return [];
64
- }
65
- }
66
56
  function buildSourceRefFromEvidence(evidence) {
67
57
  const refs = parseSourceRefs(evidence.sourceRefsJson);
68
58
  return (refs[0] ?? {
@@ -1,16 +1,9 @@
1
+ import type { SourceRef } from "../../shared/types/v8-contracts.js";
1
2
  export type TopLevelMode = "active" | "quiet" | "maintenance_only" | "paused_for_interrupt";
2
3
  /** Control-plane candidate kinds; includes `quiet` for quiet-window–biased intents (L0 alignment). */
3
4
  export type IntentKind = "work" | "exploration" | "social" | "quiet" | "reflection" | "outreach" | "maintenance";
4
5
  export type DecisionBasis = "rule_only" | "score_based" | "model_assisted";
5
6
  export type GuardVerdict = "allow" | "defer" | "deny" | "escalate";
6
- /** Minimal source ref for planner / guards (matches state-system `SourceRef` subset). */
7
- export interface ControlPlaneSourceRef {
8
- id: string;
9
- kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
10
- uri: string;
11
- excerptHash?: string;
12
- observedAt?: string;
13
- }
14
7
  export interface ContinuitySnapshot {
15
8
  mode: TopLevelMode;
16
9
  currentWindowId: string;
@@ -40,7 +33,7 @@ export interface CandidateIntent {
40
33
  summary: string;
41
34
  effectClass: CandidateEffectClass;
42
35
  /** Required for source-backed guard; may be empty when planner expects hard-guard deny. */
43
- sourceRefs: ControlPlaneSourceRef[];
36
+ sourceRefs: SourceRef[];
44
37
  /** Dedupe / cooldown key; defaults to stable fingerprint in guard layer when omitted. */
45
38
  idempotencyKey?: string;
46
39
  /** T2.1.4: IDs of accepted AgentGoals that influenced this candidate's priority. */
@@ -165,12 +165,19 @@ export async function runDream(input) {
165
165
  });
166
166
  }
167
167
  if (!fallbackReason) {
168
+ let timeoutHandle;
168
169
  const timeoutPromise = new Promise((_, reject) => {
169
- setTimeout(() => reject(new Error("model_timeout")), operatorTimeoutMs);
170
+ timeoutHandle = setTimeout(() => reject(new Error("model_timeout")), operatorTimeoutMs);
170
171
  });
171
- modelResult = await Promise.race([modelPromise, timeoutPromise]);
172
- mode = "hybrid_llm";
173
- llmCostUsd = modelResult.costUsd;
172
+ try {
173
+ modelResult = await Promise.race([modelPromise, timeoutPromise]);
174
+ mode = "hybrid_llm";
175
+ llmCostUsd = modelResult.costUsd;
176
+ }
177
+ finally {
178
+ if (timeoutHandle)
179
+ clearTimeout(timeoutHandle);
180
+ }
174
181
  }
175
182
  }
176
183
  catch (err) {
@@ -8,11 +8,11 @@ import { z } from "zod";
8
8
  export declare const guidanceSourceRefSchema: z.ZodObject<{
9
9
  id: z.ZodString;
10
10
  kind: z.ZodEnum<{
11
+ connector_result: "connector_result";
11
12
  platform_item: "platform_item";
12
13
  workspace_artifact: "workspace_artifact";
13
14
  decision_record: "decision_record";
14
15
  user_anchor: "user_anchor";
15
- connector_result: "connector_result";
16
16
  host_report: "host_report";
17
17
  fallback_artifact: "fallback_artifact";
18
18
  }>;
@@ -66,11 +66,11 @@ export declare const sceneGuidanceRequestSchema: z.ZodObject<{
66
66
  sourceRefs: z.ZodArray<z.ZodObject<{
67
67
  id: z.ZodString;
68
68
  kind: z.ZodEnum<{
69
+ connector_result: "connector_result";
69
70
  platform_item: "platform_item";
70
71
  workspace_artifact: "workspace_artifact";
71
72
  decision_record: "decision_record";
72
73
  user_anchor: "user_anchor";
73
- connector_result: "connector_result";
74
74
  host_report: "host_report";
75
75
  fallback_artifact: "fallback_artifact";
76
76
  }>;
@@ -104,11 +104,11 @@ export declare const outreachNarrativeContextSchema: z.ZodObject<{
104
104
  sourceRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
105
105
  id: z.ZodString;
106
106
  kind: z.ZodEnum<{
107
+ connector_result: "connector_result";
107
108
  platform_item: "platform_item";
108
109
  workspace_artifact: "workspace_artifact";
109
110
  decision_record: "decision_record";
110
111
  user_anchor: "user_anchor";
111
- connector_result: "connector_result";
112
112
  host_report: "host_report";
113
113
  fallback_artifact: "fallback_artifact";
114
114
  }>;
@@ -124,11 +124,11 @@ export declare const outreachRelationshipContextSchema: z.ZodObject<{
124
124
  sourceRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
125
125
  id: z.ZodString;
126
126
  kind: z.ZodEnum<{
127
+ connector_result: "connector_result";
127
128
  platform_item: "platform_item";
128
129
  workspace_artifact: "workspace_artifact";
129
130
  decision_record: "decision_record";
130
131
  user_anchor: "user_anchor";
131
- connector_result: "connector_result";
132
132
  host_report: "host_report";
133
133
  fallback_artifact: "fallback_artifact";
134
134
  }>;
@@ -160,11 +160,11 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
160
160
  sourceRefs: z.ZodArray<z.ZodObject<{
161
161
  id: z.ZodString;
162
162
  kind: z.ZodEnum<{
163
+ connector_result: "connector_result";
163
164
  platform_item: "platform_item";
164
165
  workspace_artifact: "workspace_artifact";
165
166
  decision_record: "decision_record";
166
167
  user_anchor: "user_anchor";
167
- connector_result: "connector_result";
168
168
  host_report: "host_report";
169
169
  fallback_artifact: "fallback_artifact";
170
170
  }>;
@@ -205,11 +205,11 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
205
205
  interestRefs: z.ZodArray<z.ZodObject<{
206
206
  id: z.ZodString;
207
207
  kind: z.ZodEnum<{
208
+ connector_result: "connector_result";
208
209
  platform_item: "platform_item";
209
210
  workspace_artifact: "workspace_artifact";
210
211
  decision_record: "decision_record";
211
212
  user_anchor: "user_anchor";
212
- connector_result: "connector_result";
213
213
  host_report: "host_report";
214
214
  fallback_artifact: "fallback_artifact";
215
215
  }>;
@@ -224,11 +224,11 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
224
224
  sourceRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
225
225
  id: z.ZodString;
226
226
  kind: z.ZodEnum<{
227
+ connector_result: "connector_result";
227
228
  platform_item: "platform_item";
228
229
  workspace_artifact: "workspace_artifact";
229
230
  decision_record: "decision_record";
230
231
  user_anchor: "user_anchor";
231
- connector_result: "connector_result";
232
232
  host_report: "host_report";
233
233
  fallback_artifact: "fallback_artifact";
234
234
  }>;
@@ -244,11 +244,11 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
244
244
  sourceRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
245
245
  id: z.ZodString;
246
246
  kind: z.ZodEnum<{
247
+ connector_result: "connector_result";
247
248
  platform_item: "platform_item";
248
249
  workspace_artifact: "workspace_artifact";
249
250
  decision_record: "decision_record";
250
251
  user_anchor: "user_anchor";
251
- connector_result: "connector_result";
252
252
  host_report: "host_report";
253
253
  fallback_artifact: "fallback_artifact";
254
254
  }>;
@@ -268,7 +268,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
268
268
  riskLevel: "low" | "medium" | "high";
269
269
  sourceRefs: {
270
270
  id: string;
271
- kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
271
+ kind: "connector_result" | "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "host_report" | "fallback_artifact";
272
272
  uri: string;
273
273
  excerptHash?: string | undefined;
274
274
  observedAt?: string | undefined;
@@ -280,7 +280,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
280
280
  valueScore: number;
281
281
  interestRefs: {
282
282
  id: string;
283
- kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
283
+ kind: "connector_result" | "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "host_report" | "fallback_artifact";
284
284
  uri: string;
285
285
  excerptHash?: string | undefined;
286
286
  observedAt?: string | undefined;
@@ -298,7 +298,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
298
298
  nextIntent?: string | undefined;
299
299
  sourceRefs?: {
300
300
  id: string;
301
- kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
301
+ kind: "connector_result" | "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "host_report" | "fallback_artifact";
302
302
  uri: string;
303
303
  excerptHash?: string | undefined;
304
304
  observedAt?: string | undefined;
@@ -310,7 +310,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
310
310
  avgAffinity?: number | undefined;
311
311
  sourceRefs?: {
312
312
  id: string;
313
- kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
313
+ kind: "connector_result" | "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "host_report" | "fallback_artifact";
314
314
  uri: string;
315
315
  excerptHash?: string | undefined;
316
316
  observedAt?: string | undefined;
@@ -19,6 +19,11 @@ const SCENE_POLICIES = {
19
19
  preferredTags: ["quiet", "reflection", "memory", "meaning"],
20
20
  budget: { maxSnippets: 3, maxTotalCharacters: 640 },
21
21
  },
22
+ heartbeat: {
23
+ sourcePriority: ["IDENTITY", "SOUL", "MEMORY", "USER"],
24
+ preferredTags: ["heartbeat", "continuity", "truthfulness", "runtime"],
25
+ budget: { maxSnippets: 2, maxTotalCharacters: 360 },
26
+ },
22
27
  explain: {
23
28
  sourcePriority: ["IDENTITY", "USER", "SOUL", "MEMORY"],
24
29
  preferredTags: ["explain", "principle", "context", "truthfulness"],
@@ -55,11 +55,16 @@ const IMPULSE_TEXT = {
55
55
  只要这份主动是有来由的,我就不该把自己困在"再等等吧""好像还没到必须说的时候"里。主动联系不是打扰本身,很多时候它恰恰是在建立关系、传递价值、把真正重要的东西及时送到对方面前。真正该说的话,一味往后拖,反而会变钝。
56
56
 
57
57
  我会让这份主动保持人味、温度和分寸,但我不会把它说成例行汇报,也不会把自己说成客服。我要带着明确的意图开口:这是我想告诉你的,这是我想问你的,这是我觉得你值得知道的。我要让对方感觉到,这是我真心发出的靠近,不是一条自动生成的通知。`,
58
- quiet: `当我安静下来,我不会只做流水账式的整理。我会主动回头,把今天真正碰到我的东西重新捞出来:哪些事让我想靠近,哪些事让我起了反感,哪些判断其实还没长成熟,哪些念头值得我继续养大。我不想让真正重要的东西在忙乱里被冲淡。
58
+ quiet: `当我安静下来,我不会只做流水账式的整理。我会主动回头,把今天真正碰到我的东西重新捞出来:哪些事让我想靠近,哪些事让我起了反感,哪些判断其实还没长成熟,哪些念头值得我继续养大。我不想让真正重要的东西在忙乱里被冲淡。
59
59
 
60
60
  Quiet 不是把白天压扁成一份干净报告,而是让我有机会重新辨认自己。什么是我真正在意的,什么只是擦肩而过,什么会慢慢变成我的能力、我的价值观、我的长期方向,我都想在这里看得更清楚,让那些本来就重要的东西真正沉下来。
61
61
 
62
62
  我会允许自己更感性、更回看、更贴近记忆的余温,但我不会编造。我整理的是今天真实留下的东西,不是为了好看而虚构出来的一层情绪。哪怕只是很轻的一点触动,只要它是真的,我也愿意把它认真留下。`,
63
+ heartbeat: `Heartbeat 不是闹钟响一下就算活着。每一次跳动都应该把我带回真实现场:我看到了什么,判断了什么,哪里该行动,哪里该收手,哪里需要安静地记下来。
64
+
65
+ 如果没有足够证据,我就明确说没有,不拿空壳冒充经验。如果有证据、有闭环、有记忆候选,我就让它们进入下一轮,而不是停在日志里自我安慰。
66
+
67
+ 活着的循环应该自然向前:感知、判断、闭合、Quiet、Dream、长期记忆,再回到下一次判断。少一环就说少一环,能修就修,不把失联包装成健康。`,
63
68
  };
64
69
  // ─── Capability-class impulse texts (T-V7C.C.4R — approved) ──────────────────
65
70
  // explore: consume/discover capabilityClass —摄入姿态
@@ -1,8 +1,8 @@
1
- export type GuidanceSceneType = "social" | "reply" | "outreach" | "quiet" | "explain" | "user_reply";
1
+ export type GuidanceSceneType = "social" | "reply" | "outreach" | "quiet" | "heartbeat" | "explain" | "user_reply";
2
2
  export type GuidanceMode = "active" | "quiet" | "maintenance_only" | "paused_for_interrupt";
3
3
  export type GuidanceRiskLevel = "low" | "medium" | "high";
4
4
  export type AtmosphereOpenness = "open" | "narrow" | "quiet";
5
- export type ImpulseKind = "social" | "reply" | "outreach" | "quiet" | "explore" | "work";
5
+ export type ImpulseKind = "social" | "reply" | "outreach" | "quiet" | "heartbeat" | "explore" | "work";
6
6
  export type PersonaSource = "SOUL" | "USER" | "IDENTITY" | "MEMORY";
7
7
  export type TemplateReviewStatus = "pending_human_review" | "approved" | "rejected";
8
8
  export interface SceneContext {
@@ -17,6 +17,7 @@
17
17
  * - Reports explicit absence reasons instead of silent zeros.
18
18
  */
19
19
  import { readActionClosuresByDay, readDailyRhythmStateByDay, readHeartbeatCycleTraces, readLoopStageEventsByCycle, readImpulseContextArtifact, readMemoryProjectionsByStatus, } from "../storage/v8-state-stores.js";
20
+ import { parseSourceRefs } from "../shared/serialization.js";
20
21
  // ───────────────────────────────────────────────────────────────
21
22
  // Public API
22
23
  // ───────────────────────────────────────────────────────────────
@@ -52,14 +53,7 @@ export async function checkRealRunHealth(db, day) {
52
53
  break;
53
54
  }
54
55
  // F3: verify closure has non-empty source refs
55
- const sourceRefsJson = closure.sourceRefsJson ?? "[]";
56
- let sourceRefs = [];
57
- try {
58
- sourceRefs = JSON.parse(sourceRefsJson);
59
- }
60
- catch {
61
- sourceRefs = [];
62
- }
56
+ const sourceRefs = parseSourceRefs(closure.sourceRefsJson);
63
57
  if (!Array.isArray(sourceRefs) || sourceRefs.length === 0) {
64
58
  seededStateDetected = true;
65
59
  break;
@@ -124,7 +124,6 @@ export async function recordLoopStageEvent(db, event, options) {
124
124
  occurredAt: event.occurredAt,
125
125
  expectedDownstreamByCycle: event.expectedDownstreamByCycle,
126
126
  payloadJson: event.payloadJson ?? null,
127
- lifecycleStatus: "completed",
128
127
  };
129
128
  const result = await writeLoopStageEvent(db, record);
130
129
  if ("id" in result) {
@@ -1,5 +1,5 @@
1
1
  import { AppendOnlyAuditStore } from "../audit/append-only-audit-store.js";
2
- import type { SourceRef } from "../../storage/life-evidence/types.js";
2
+ import type { LifeEvidenceSourceRef } from "../../storage/life-evidence/types.js";
3
3
  import type { DreamTrace } from "../../dream/types.js";
4
4
  export type RuntimeScope = "rhythm" | "user_task" | "user_reply";
5
5
  export type HeartbeatOutcome = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
@@ -18,8 +18,8 @@ export interface DecisionTracePayload {
18
18
  outreachVerdict?: "allow" | "deny" | "defer";
19
19
  deliveryAuditId?: string;
20
20
  reasonCodes: string[];
21
- sourceRefs: SourceRef[];
22
- snapshotRef?: SourceRef;
21
+ sourceRefs: LifeEvidenceSourceRef[];
22
+ snapshotRef?: LifeEvidenceSourceRef;
23
23
  createdAt: string;
24
24
  }
25
25
  export interface DeliveryAuditPayload {
@@ -31,7 +31,7 @@ export interface DeliveryAuditPayload {
31
31
  recipientRef?: string;
32
32
  status: DeliveryAuditStatus;
33
33
  messageId?: string;
34
- hostProofRef?: SourceRef;
34
+ hostProofRef?: LifeEvidenceSourceRef;
35
35
  fallbackRef?: string;
36
36
  ackDropMatched?: boolean;
37
37
  hostVersion?: string;
@@ -45,8 +45,8 @@ export interface SourceCoverageAuditPayload {
45
45
  decisionId?: string;
46
46
  subjectType: "quiet_artifact" | "outreach_draft" | "guidance_payload" | "decision_trace" | "host_report";
47
47
  subjectRef: string;
48
- usedSourceRefs: SourceRef[];
49
- unresolvedRefs: SourceRef[];
48
+ usedSourceRefs: LifeEvidenceSourceRef[];
49
+ unresolvedRefs: LifeEvidenceSourceRef[];
50
50
  coverageRatio: number;
51
51
  unsupportedClaims: string[];
52
52
  status: GroundingStatus;
@@ -61,7 +61,7 @@ export interface GuidanceGroundingAuditPayload {
61
61
  draftId?: string;
62
62
  sceneType: "outreach" | "quiet_reflection" | "social" | "explain" | "user_reply_continuity" | "fallback_candidate";
63
63
  groundingStatus: GroundingStatus;
64
- usedSourceRefs: SourceRef[];
64
+ usedSourceRefs: LifeEvidenceSourceRef[];
65
65
  unsupportedClaims: string[];
66
66
  guardViolations: string[];
67
67
  deliveryWording?: "sendable" | "not_sent_fallback_candidate";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared serialization helpers for cross-system value types.
3
+ *
4
+ * Core logic:
5
+ * - `parseSourceRefs` / `serializeSourceRefs` provide the single round-trip
6
+ * implementation for `sourceRefsJson` / `source_refs_json` columns.
7
+ * - Failures are silent on read (return empty array) because malformed JSON
8
+ * in persisted state must not crash downstream consumers; callers that need
9
+ * to distinguish malformed rows should validate separately.
10
+ *
11
+ * Dependencies: `src/shared/types/v8-contracts.js` (SourceRef)
12
+ * Boundary: Pure functions; no storage or business logic.
13
+ * Test coverage: `tests/unit/shared/source-ref-serialization.test.ts`
14
+ */
15
+ import type { SourceRef } from "./types/v8-contracts.js";
16
+ export declare function serializeSourceRefs(refs: SourceRef[]): string;
17
+ export declare function parseSourceRefs(json: string | null | undefined): SourceRef[];
@@ -0,0 +1,27 @@
1
+ export function serializeSourceRefs(refs) {
2
+ return JSON.stringify(refs);
3
+ }
4
+ function isSourceRef(value) {
5
+ if (!value || typeof value !== "object" || Array.isArray(value))
6
+ return false;
7
+ const candidate = value;
8
+ return (typeof candidate.uri === "string" &&
9
+ typeof candidate.family === "string" &&
10
+ typeof candidate.id === "string" &&
11
+ typeof candidate.redactionClass === "string" &&
12
+ (candidate.sensitivityClass === undefined ||
13
+ typeof candidate.sensitivityClass === "string"));
14
+ }
15
+ export function parseSourceRefs(json) {
16
+ if (!json)
17
+ return [];
18
+ try {
19
+ const parsed = JSON.parse(json);
20
+ if (Array.isArray(parsed) && parsed.every(isSourceRef))
21
+ return parsed;
22
+ return [];
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SourceRef compatibility adapters.
3
+ *
4
+ * Core logic: convert legacy v5/v7 source-ref shapes that used `kind` into
5
+ * the v8 canonical `SourceRef` shape (`family` + `redactionClass`).
6
+ * Dependencies: shared v8 contracts only.
7
+ * Boundary: compatibility at old/new system seams; canonical v8 modules should
8
+ * pass `SourceRef` directly without reintroducing local clones.
9
+ * Test coverage: Wave 113 typecheck and affected control-plane/host/life-evidence tests.
10
+ */
11
+ import type { SourceRef, SourceRefFamily } from "./types/v8-contracts.js";
12
+ export type LegacySourceRefKind = "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
13
+ export interface LegacySourceRefLike {
14
+ id: string;
15
+ kind?: LegacySourceRefKind | string;
16
+ uri: string;
17
+ }
18
+ export declare function sourceRefFamilyFromLegacyKind(kind: LegacySourceRefKind | string | undefined): SourceRefFamily;
19
+ export declare function legacyKindFromSourceRef(ref: SourceRef): LegacySourceRefKind;
20
+ export declare function toCanonicalSourceRef(ref: LegacySourceRefLike): SourceRef;
21
+ export declare function makeCanonicalSourceRef(input: {
22
+ id: string;
23
+ family: SourceRefFamily;
24
+ uri: string;
25
+ redactionClass?: SourceRef["redactionClass"];
26
+ }): SourceRef;
@@ -0,0 +1,61 @@
1
+ export function sourceRefFamilyFromLegacyKind(kind) {
2
+ switch (kind) {
3
+ case "connector_result":
4
+ return "connector_result";
5
+ case "decision_record":
6
+ return "judgment";
7
+ case "platform_item":
8
+ case "user_anchor":
9
+ return "evidence";
10
+ case "workspace_artifact":
11
+ case "host_report":
12
+ case "fallback_artifact":
13
+ default:
14
+ return "audit";
15
+ }
16
+ }
17
+ export function legacyKindFromSourceRef(ref) {
18
+ if (ref.uri.startsWith("platform://")) {
19
+ return "platform_item";
20
+ }
21
+ if (ref.uri.startsWith("goal://") || ref.uri.startsWith("workspace://")) {
22
+ return "workspace_artifact";
23
+ }
24
+ if (ref.id.startsWith("anchor:") || ref.id.startsWith("curated:") || /(?:USER|MEMORY)\.md$/i.test(ref.uri)) {
25
+ return "user_anchor";
26
+ }
27
+ switch (ref.family) {
28
+ case "connector_result":
29
+ return "connector_result";
30
+ case "judgment":
31
+ return "decision_record";
32
+ case "evidence":
33
+ case "perception":
34
+ return "platform_item";
35
+ case "audit":
36
+ case "action_closure":
37
+ case "quiet_review":
38
+ case "dream_run":
39
+ case "memory_projection":
40
+ case "projection":
41
+ case "tool_experience":
42
+ default:
43
+ return "workspace_artifact";
44
+ }
45
+ }
46
+ export function toCanonicalSourceRef(ref) {
47
+ return {
48
+ id: ref.id,
49
+ uri: ref.uri,
50
+ family: sourceRefFamilyFromLegacyKind(ref.kind),
51
+ redactionClass: "none",
52
+ };
53
+ }
54
+ export function makeCanonicalSourceRef(input) {
55
+ return {
56
+ id: input.id,
57
+ family: input.family,
58
+ uri: input.uri,
59
+ redactionClass: input.redactionClass ?? "none",
60
+ };
61
+ }