@haaaiawd/second-nature 0.2.9 → 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 (84) hide show
  1. package/index.js +6 -0
  2. package/openclaw.plugin.json +2 -5
  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/ops-router.js +3 -3
  10. package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
  11. package/runtime/connectors/manifest/manifest-schema.d.ts +2 -2
  12. package/runtime/connectors/registry/dynamic-connector-registry.js +16 -1
  13. package/runtime/connectors/services/connector-executor-adapter.js +54 -35
  14. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +2 -0
  15. package/runtime/core/second-nature/action/action-closure-recorder.js +2 -4
  16. package/runtime/core/second-nature/action/action-proposal-builder.js +5 -32
  17. package/runtime/core/second-nature/action/policy-bound-dispatch.js +4 -3
  18. package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +1 -11
  19. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +1 -0
  20. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +10 -6
  21. package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
  22. package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -0
  23. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +4 -3
  24. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +3 -2
  25. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -2
  26. package/runtime/core/second-nature/orchestrator/intent-planner.js +4 -2
  27. package/runtime/core/second-nature/orchestrator/narrative-update.js +1 -2
  28. package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +2 -2
  29. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +1 -1
  30. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +2 -3
  31. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +2 -2
  32. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +6 -1
  33. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +3 -14
  34. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +6 -5
  35. package/runtime/core/second-nature/perception/judgment-engine.js +2 -12
  36. package/runtime/core/second-nature/perception/perception-builder.js +1 -9
  37. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +13 -15
  38. package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +10 -13
  39. package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +0 -2
  40. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +0 -12
  41. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +1 -11
  42. package/runtime/core/second-nature/types.d.ts +2 -9
  43. package/runtime/dream/dream-engine.js +11 -4
  44. package/runtime/guidance/outreach-draft-schema.d.ts +12 -12
  45. package/runtime/guidance/persona-selection.js +5 -0
  46. package/runtime/guidance/template-registry.js +6 -1
  47. package/runtime/guidance/types.d.ts +2 -2
  48. package/runtime/observability/living-loop-health-gate.js +2 -8
  49. package/runtime/observability/loop-stage-event-sink.js +0 -1
  50. package/runtime/observability/services/lived-experience-audit.d.ts +7 -7
  51. package/runtime/shared/serialization.d.ts +17 -0
  52. package/runtime/shared/serialization.js +27 -0
  53. package/runtime/shared/source-ref-compat.d.ts +26 -0
  54. package/runtime/shared/source-ref-compat.js +61 -0
  55. package/runtime/shared/types/goal.d.ts +4 -4
  56. package/runtime/shared/types/goal.js +1 -1
  57. package/runtime/shared/types/index.d.ts +1 -0
  58. package/runtime/shared/types/index.js +1 -3
  59. package/runtime/shared/types/source-ref.d.ts +2 -2
  60. package/runtime/shared/types/v7-entities.d.ts +5 -5
  61. package/runtime/shared/types/v7-entities.js +1 -1
  62. package/runtime/shared/types/v8-contracts.d.ts +1 -1
  63. package/runtime/storage/db/index.js +31 -26
  64. package/runtime/storage/db/migrations/index.js +4 -0
  65. package/runtime/storage/db/migrations/v8-004-schema-closure.d.ts +19 -0
  66. package/runtime/storage/db/migrations/v8-004-schema-closure.js +74 -0
  67. package/runtime/storage/db/migrations/v8-005-single-status-schema.d.ts +11 -0
  68. package/runtime/storage/db/migrations/v8-005-single-status-schema.js +16 -0
  69. package/runtime/storage/db/schema/v8-entities.d.ts +0 -95
  70. package/runtime/storage/db/schema/v8-entities.js +4 -7
  71. package/runtime/storage/delivery/types.d.ts +2 -2
  72. package/runtime/storage/fallback/load-operator-fallback.d.ts +2 -2
  73. package/runtime/storage/fallback/operator-fallback-types.d.ts +2 -2
  74. package/runtime/storage/fallback/operator-fallback-view.d.ts +2 -2
  75. package/runtime/storage/index.d.ts +1 -1
  76. package/runtime/storage/life-evidence/types.d.ts +5 -5
  77. package/runtime/storage/quiet/quiet-artifact-types.d.ts +4 -4
  78. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +2 -2
  79. package/runtime/storage/services/write-validation-gate.d.ts +1 -1
  80. package/runtime/storage/services/write-validation-gate.js +16 -4
  81. package/runtime/storage/snapshots/types.d.ts +8 -8
  82. package/runtime/storage/user-interest/types.d.ts +3 -3
  83. package/runtime/storage/v8-state-stores.d.ts +8 -1
  84. package/runtime/storage/v8-state-stores.js +23 -20
@@ -191,7 +191,7 @@ export async function runHeartbeatCycle(db, request) {
191
191
  reason: degradedReason,
192
192
  sourceRefs: degradedClosureRef ? [degradedClosureRef, cycleRef] : [cycleRef],
193
193
  });
194
- const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
194
+ const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
195
195
  return {
196
196
  cycleId,
197
197
  cycleSequence,
@@ -206,7 +206,8 @@ export async function runHeartbeatCycle(db, request) {
206
206
  operatorNextAction: "Retry heartbeat after perception recovery",
207
207
  retryable: true,
208
208
  }
209
- : undefined,
209
+ : rhythmDegraded,
210
+ rhythmDegraded,
210
211
  rhythmState,
211
212
  };
212
213
  }
@@ -245,12 +246,14 @@ export async function runHeartbeatCycle(db, request) {
245
246
  reason: "evidence_batch_empty",
246
247
  sourceRefs: emptyClosureRef ? [emptyClosureRef, cycleRef] : [cycleRef],
247
248
  });
248
- const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
249
+ const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
249
250
  return {
250
251
  cycleId,
251
252
  cycleSequence,
252
253
  closureRef: emptyClosureRef,
253
254
  noActionReason: "evidence_batch_empty",
255
+ degraded: rhythmDegraded,
256
+ rhythmDegraded,
254
257
  rhythmState,
255
258
  };
256
259
  }
@@ -365,7 +368,7 @@ export async function runHeartbeatCycle(db, request) {
365
368
  }
366
369
  else if (proposalResult.status === "remember_for_review") {
367
370
  const remember = proposalResult;
368
- const closureResult = await recordRememberClosure(db, cycleId, remember.memoryReviewCandidate, { now });
371
+ const closureResult = await recordRememberClosure(db, cycleId, remember.memoryReviewCandidate, { now, platformId: "heartbeat" });
369
372
  if ("closureId" in closureResult) {
370
373
  closureRef = {
371
374
  uri: `sn://closure/${closureResult.closureId}`,
@@ -540,13 +543,14 @@ export async function runHeartbeatCycle(db, request) {
540
543
  noActionReason = "proposal_no_action";
541
544
  }
542
545
  // T-CP.R.3: Advance daily rhythm after closure/no-action
543
- const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
546
+ const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
544
547
  return {
545
548
  cycleId,
546
549
  cycleSequence,
547
550
  closureRef,
548
551
  noActionReason,
549
- degraded: closureDegraded,
552
+ degraded: closureDegraded ?? rhythmDegraded,
553
+ rhythmDegraded,
550
554
  rhythmState,
551
555
  };
552
556
  }
@@ -31,5 +31,7 @@ export interface RealRuntimeSpineResult {
31
31
  noActionReason?: V8ReasonCode;
32
32
  degraded?: DegradedOperationResult;
33
33
  rhythmState?: DailyRhythmState;
34
+ rhythmDegraded?: DegradedOperationResult;
35
+ impulseContextArtifactId?: string;
34
36
  }
35
37
  export declare function runRealRuntimeHeartbeatCycle(options: RealRuntimeSpineOptions): Promise<RealRuntimeSpineResult | DegradedOperationResult>;
@@ -38,5 +38,6 @@ export async function runRealRuntimeHeartbeatCycle(options) {
38
38
  noActionReason: orchestrationResult.noActionReason,
39
39
  degraded: orchestrationResult.degraded,
40
40
  rhythmState: orchestrationResult.rhythmState,
41
+ rhythmDegraded: orchestrationResult.rhythmDegraded,
41
42
  };
42
43
  }
@@ -12,6 +12,7 @@ import { mapLifeEvidence } from "../../../connectors/base/map-life-evidence.js";
12
12
  import { appendLifeEvidence } from "../../../storage/life-evidence/append-life-evidence.js";
13
13
  import { normalizeConnectorEvidence } from "../../../connectors/evidence-normalizer.js";
14
14
  import { recordConnectorAttemptAudit } from "../../../observability/services/audit-closure-recorders.js";
15
+ import { makeCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
15
16
  /**
16
17
  * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
17
18
  * Exported for unit tests (CR-M1 wiring).
@@ -302,11 +303,11 @@ export async function ingestRhythmSignal(signal, deps) {
302
303
  effectClass: "connector_action",
303
304
  capabilityIntent: idleResult.candidate.capabilityId,
304
305
  sourceRefs: [
305
- {
306
+ makeCanonicalSourceRef({
306
307
  id: "idle_curiosity",
307
- kind: "workspace_artifact",
308
+ family: "audit",
308
309
  uri: `idle://${idleResult.candidate.platformId}`,
309
- },
310
+ }),
310
311
  ],
311
312
  idempotencyKey: `idle:${idleResult.candidate.platformId}:${idleResult.candidate.capabilityId}`,
312
313
  goalInfluenceRefs: [],
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * HeartbeatRuntimeSnapshot assembly for candidate planner + hard guards (T2.1.3, T2.2.1).
3
3
  */
4
- import type { ContinuitySnapshot, ControlPlaneSourceRef } from "../types.js";
4
+ import type { ContinuitySnapshot } from "../types.js";
5
+ import type { SourceRef } from "../../../shared/types/v8-contracts.js";
5
6
  import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
6
7
  import { type PlannerRhythmWindowSlice } from "../rhythm/planner-rhythm-window.js";
7
8
  import type { SnapshotInputs } from "./snapshot-builder.js";
8
9
  import type { AffordanceMap } from "../../../shared/types/v7-entities.js";
9
10
  export interface PlannerLifeEvidenceSlice {
10
- evidenceRefs: ControlPlaneSourceRef[];
11
+ evidenceRefs: SourceRef[];
11
12
  platformEventCount: number;
12
13
  workEventCount: number;
13
14
  emptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * Per design doc §4.2: SnapshotBuilder prepares inputs for the Rhythm Engine.
8
8
  */
9
- import type { ContinuitySnapshot, ControlPlaneSourceRef, TopLevelMode } from "../types.js";
9
+ import type { ContinuitySnapshot, TopLevelMode } from "../types.js";
10
+ import type { SourceRef } from "../../../shared/types/v8-contracts.js";
10
11
  import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
11
12
  import type { DeliveryCapabilitySnapshot } from "../outreach/delivery-target.js";
12
13
  import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
@@ -30,7 +31,7 @@ export interface SnapshotInputs {
30
31
  awaitingUserInput?: boolean;
31
32
  riskSuppressed?: boolean;
32
33
  /** Evidence refs for source-backed planner/guards (T2.1.3 / T2.2.1). */
33
- lifeEvidenceRefs?: ControlPlaneSourceRef[];
34
+ lifeEvidenceRefs?: SourceRef[];
34
35
  platformEventCount?: number;
35
36
  workEventCount?: number;
36
37
  lifeEvidenceEmptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
@@ -1,10 +1,11 @@
1
+ import { makeCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
1
2
  import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
2
3
  import { buildHeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
3
4
  import { resolvePlatformForIntent, } from "./platform-capability-router.js";
4
5
  import { isGoalRelatedToCandidate } from "./goal-priority.js";
5
6
  const MAX_CANDIDATE_INTENTS = 6;
6
7
  const OBLIGATION_SOURCE = [
7
- { id: "obligation-anchor", kind: "workspace_artifact", uri: "workspace://obligations/pending" },
8
+ makeCanonicalSourceRef({ id: "obligation-anchor", family: "audit", uri: "workspace://obligations/pending" }),
8
9
  ];
9
10
  function evidenceRefsForConnector(runtime) {
10
11
  if (!isLifeEvidenceSliceEmpty(runtime.lifeEvidence) && runtime.lifeEvidence.evidenceRefs.length > 0) {
@@ -253,8 +254,9 @@ export function planCandidateIntents(runtime, options) {
253
254
  if (intent.sourceRefs.length === 0 && related.length > 0) {
254
255
  intent.sourceRefs = related.slice(0, 4).map((g) => ({
255
256
  id: g.goalId,
256
- kind: "workspace_artifact",
257
+ family: "audit",
257
258
  uri: `goal://${g.goalId}`,
259
+ redactionClass: "none",
258
260
  }));
259
261
  }
260
262
  }
@@ -3,9 +3,8 @@ const DEFAULT_NARRATIVE_ID = "default";
3
3
  function mapControlPlaneRefToSourceRef(ref) {
4
4
  return {
5
5
  sourceId: ref.id,
6
- kind: ref.kind,
6
+ kind: ref.family,
7
7
  url: ref.uri,
8
- snippet: ref.excerptHash,
9
8
  };
10
9
  }
11
10
  /**
@@ -12,7 +12,7 @@
12
12
  * - Optional registry: when absent, resolution is best-effort from goals/evidence.
13
13
  */
14
14
  import type { IntentKind } from "../types.js";
15
- import type { ControlPlaneSourceRef } from "../types.js";
15
+ import type { SourceRef } from "../../../shared/types/v8-contracts.js";
16
16
  import type { CapabilityContractRegistry } from "../../../connectors/base/manifest.js";
17
17
  /** Minimal goal shape accepted by the router to avoid coupling to AgentGoal. M-03 decoupling. */
18
18
  interface GoalRouterContext {
@@ -24,7 +24,7 @@ export interface PlatformResolutionContext {
24
24
  /** Accepted goals that may name a platform or capability. */
25
25
  acceptedGoals?: GoalRouterContext[];
26
26
  /** Evidence refs that may embed platform identity. */
27
- evidenceRefs?: ControlPlaneSourceRef[];
27
+ evidenceRefs?: SourceRef[];
28
28
  }
29
29
  /**
30
30
  * Resolve an explicit platformId for a candidate intent kind.
@@ -48,7 +48,7 @@ function extractPlatformIdsFromGoals(goals, kind, platformIds) {
48
48
  function extractPlatformIdsFromEvidence(refs, platformIds) {
49
49
  const results = new Set();
50
50
  for (const ref of refs) {
51
- if (ref.kind === "connector_result" && ref.id) {
51
+ if (ref.family === "connector_result" && ref.id) {
52
52
  for (const pid of platformIds) {
53
53
  if (ref.id.includes(pid)) {
54
54
  results.add(pid);
@@ -3,6 +3,7 @@
3
3
  * Aligns with control-plane-system.detail §3.9 buildOutreachDraftRequest.
4
4
  */
5
5
  import * as crypto from "node:crypto";
6
+ import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
6
7
  function inferRhythmWindowKind(windowId) {
7
8
  const id = windowId.toLowerCase();
8
9
  if (id.includes("work"))
@@ -20,10 +21,8 @@ function inferRhythmWindowKind(windowId) {
20
21
  function toGuidanceRefs(refs) {
21
22
  return refs.map((r) => ({
22
23
  id: r.id,
23
- kind: r.kind,
24
+ kind: legacyKindFromSourceRef(r),
24
25
  uri: r.uri,
25
- excerptHash: r.excerptHash,
26
- observedAt: r.observedAt,
27
26
  }));
28
27
  }
29
28
  function mapDeliveryVerdict(verdict) {
@@ -7,7 +7,7 @@ import type { CandidateIntent } from "../types.js";
7
7
  import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
8
8
  import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
9
9
  import type { StateDatabase } from "../../../storage/db/index.js";
10
- import type { SourceRef } from "../../../storage/life-evidence/types.js";
10
+ import type { LifeEvidenceSourceRef } from "../../../storage/life-evidence/types.js";
11
11
  import { type JudgeOutreachInput } from "./judge-outreach.js";
12
12
  import { type DeliveryTargetResolution } from "./delivery-target.js";
13
13
  export interface OpenClawDeliverySendResult {
@@ -16,7 +16,7 @@ export interface OpenClawDeliverySendResult {
16
16
  errorClass?: string;
17
17
  messageId?: string;
18
18
  /** Host-reported delivery proof when messageId is absent (T4.3.1). */
19
- hostProofRef?: SourceRef;
19
+ hostProofRef?: LifeEvidenceSourceRef;
20
20
  }
21
21
  export interface OpenClawDeliveryPort {
22
22
  sendDeliveryRequest(input: {
@@ -1,3 +1,4 @@
1
+ import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
1
2
  import { writeDeliveryAttempt } from "../../../storage/delivery/write-delivery-attempt.js";
2
3
  import { writeOperatorFallback } from "../../../storage/fallback/write-operator-fallback.js";
3
4
  import { judgeOutreach } from "./judge-outreach.js";
@@ -6,7 +7,11 @@ import { buildOutreachDraftRequest } from "./build-outreach-draft-request.js";
6
7
  import { createNarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
7
8
  import { createRelationshipMemoryStore } from "../../../storage/relationship/relationship-memory-store.js";
8
9
  function toSourceRefs(refs) {
9
- return refs.map((r) => ({ ...r }));
10
+ return refs.map((r) => ({
11
+ id: r.id,
12
+ kind: legacyKindFromSourceRef(r),
13
+ uri: r.uri,
14
+ }));
10
15
  }
11
16
  function hasDeliveryProof(attempt) {
12
17
  return Boolean(attempt.messageId?.trim()) || Boolean(attempt.hostProofRef);
@@ -1,12 +1,7 @@
1
+ import { toCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
1
2
  import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
2
3
  function toControlPlaneRefs(refs) {
3
- return refs.map((r) => ({
4
- id: r.id,
5
- kind: r.kind,
6
- uri: r.uri,
7
- excerptHash: r.excerptHash,
8
- observedAt: r.observedAt,
9
- }));
4
+ return refs.map((r) => toCanonicalSourceRef(r));
10
5
  }
11
6
  export function userInterestSnapshotToJudge(snapshot) {
12
7
  if (!snapshot) {
@@ -18,13 +13,7 @@ export function userInterestSnapshotToJudge(snapshot) {
18
13
  signals: snapshot.signals.map((s) => ({
19
14
  topic: s.topic,
20
15
  confidence: s.confidence,
21
- sourceRefs: s.sourceRefs.map((r) => ({
22
- id: r.id,
23
- kind: r.kind,
24
- uri: r.uri,
25
- excerptHash: r.excerptHash,
26
- observedAt: r.observedAt,
27
- })),
16
+ sourceRefs: s.sourceRefs.map((r) => toCanonicalSourceRef(r)),
28
17
  })),
29
18
  sourceRefs: toControlPlaneRefs(snapshot.sourceRefs),
30
19
  };
@@ -1,4 +1,5 @@
1
- import type { CandidateIntent, ControlPlaneSourceRef } from "../types.js";
1
+ import type { CandidateIntent } from "../types.js";
2
+ import type { SourceRef } from "../../../shared/types/v8-contracts.js";
2
3
  import { type DeliveryCapabilitySnapshot, type DeliveryTargetResolution } from "./delivery-target.js";
3
4
  export type OutreachJudgmentVerdict = "allow" | "deny" | "defer";
4
5
  export type CooldownState = "clear" | "cooling_down" | "duplicate";
@@ -8,9 +9,9 @@ export interface JudgeOutreachUserInterest {
8
9
  signals: Array<{
9
10
  topic: string;
10
11
  confidence: number;
11
- sourceRefs: ControlPlaneSourceRef[];
12
+ sourceRefs: SourceRef[];
12
13
  }>;
13
- sourceRefs: ControlPlaneSourceRef[];
14
+ sourceRefs: SourceRef[];
14
15
  }
15
16
  export interface JudgeOutreachLifeEvidence {
16
17
  empty: boolean;
@@ -31,8 +32,8 @@ export interface OutreachJudgment {
31
32
  valueScore: number;
32
33
  userRelevance: number;
33
34
  actionability: number;
34
- interestRefs: ControlPlaneSourceRef[];
35
- sourceRefs: ControlPlaneSourceRef[];
35
+ interestRefs: SourceRef[];
36
+ sourceRefs: SourceRef[];
36
37
  cooldownState: CooldownState;
37
38
  deliveryVerdict: DeliveryTargetResolution["verdict"];
38
39
  reasons: string[];
@@ -22,6 +22,7 @@
22
22
  * Test coverage: tests/unit/judgment/judgment-engine.test.ts
23
23
  */
24
24
  import { readPerceptionCardById, writeJudgmentVerdict, } from "../../../storage/v8-state-stores.js";
25
+ import { parseSourceRefs } from "../../../shared/serialization.js";
25
26
  import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
26
27
  // ───────────────────────────────────────────────────────────────
27
28
  // Config
@@ -42,17 +43,6 @@ function inferRiskPosture(sensitivityClass, riskFlags) {
42
43
  }
43
44
  return "low";
44
45
  }
45
- function parseCardSourceRefs(json) {
46
- if (!json)
47
- return [];
48
- try {
49
- const parsed = JSON.parse(json);
50
- return Array.isArray(parsed) ? parsed : [];
51
- }
52
- catch {
53
- return [];
54
- }
55
- }
56
46
  function selectVerdict(relevance, confidence, riskPosture, hasSourceRefs, possibleIntents) {
57
47
  // Missing source refs → ignore/watch only
58
48
  if (!hasSourceRefs) {
@@ -125,7 +115,7 @@ export async function runAgentJudgment(db, perceptionCardId, options) {
125
115
  retryable: false,
126
116
  };
127
117
  }
128
- const sourceRefs = parseCardSourceRefs(card.sourceRefsJson);
118
+ const sourceRefs = parseSourceRefs(card.sourceRefsJson);
129
119
  const hasSourceRefs = sourceRefs.length > 0;
130
120
  // Parse sensitivity class from payload (stored there by perception-builder)
131
121
  let sensitivityClass = "public_general";
@@ -22,6 +22,7 @@
22
22
  * Test coverage: tests/unit/perception/perception-builder.test.ts
23
23
  */
24
24
  import { readEvidenceItemsByStatus, writePerceptionCard, updateEvidenceItemLifecycleStatus, } from "../../../storage/v8-state-stores.js";
25
+ import { parseSourceRefs } from "../../../shared/serialization.js";
25
26
  // ───────────────────────────────────────────────────────────────
26
27
  // Config
27
28
  // ───────────────────────────────────────────────────────────────
@@ -29,15 +30,6 @@ const PERCEPTION_MAX_EVIDENCE_PER_CYCLE = 50;
29
30
  // ───────────────────────────────────────────────────────────────
30
31
  // Helpers
31
32
  // ───────────────────────────────────────────────────────────────
32
- function parseSourceRefs(json) {
33
- try {
34
- const parsed = JSON.parse(json);
35
- return Array.isArray(parsed) ? parsed : [];
36
- }
37
- catch {
38
- return [];
39
- }
40
- }
41
33
  function parsePayload(json) {
42
34
  if (!json)
43
35
  return undefined;
@@ -3,13 +3,21 @@ import { writeQuietArtifact } from "../../../storage/quiet/quiet-artifact-writer
3
3
  import { persistQuietArtifactToWorkspace } from "../../../storage/quiet/persist-quiet-artifact.js";
4
4
  import { buildEvidencePack, buildQuietNarrativeGuidance, selectInterestBasis } from "../../../guidance/evidence-guidance.js";
5
5
  import { recordQuietArtifactAudit } from "../../../observability/services/audit-closure-recorders.js";
6
+ import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
6
7
  function toGuidanceRef(r) {
7
8
  return {
8
9
  id: r.id,
9
- kind: r.kind,
10
+ kind: legacyKindFromSourceRef(r),
10
11
  uri: r.uri,
11
- excerptHash: r.excerptHash,
12
- observedAt: r.observedAt,
12
+ };
13
+ }
14
+ function toLifeEvidenceRef(ref) {
15
+ return {
16
+ id: ref.id,
17
+ kind: ref.kind,
18
+ uri: ref.uri,
19
+ excerptHash: ref.excerptHash,
20
+ observedAt: ref.observedAt,
13
21
  };
14
22
  }
15
23
  /**
@@ -116,24 +124,14 @@ export async function runSourceBackedQuiet(params) {
116
124
  confidence: userInterestSnapshot?.confidence ?? 0,
117
125
  signalCount: userInterestSnapshot?.signals.length ?? 0,
118
126
  });
119
- const groundedSourceRefs = ep.pack.groundedRefs.map((g) => ({
120
- id: g.id,
121
- kind: g.kind,
122
- uri: g.uri,
123
- excerptHash: g.excerptHash,
124
- observedAt: g.observedAt,
125
- }));
127
+ const groundedSourceRefs = ep.pack.groundedRefs.map(toLifeEvidenceRef);
126
128
  const claims = ep.pack.groundedRefs.map((g, i) => ({
127
129
  id: `fact:${g.id}`,
128
130
  text: `Evidence-backed note ${i + 1}`,
129
131
  claimType: "fact",
130
132
  sourceRefs: [
131
133
  {
132
- id: g.id,
133
- kind: g.kind,
134
- uri: g.uri,
135
- excerptHash: g.excerptHash,
136
- observedAt: g.observedAt,
134
+ ...toLifeEvidenceRef(g),
137
135
  },
138
136
  ],
139
137
  }));
@@ -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,
@@ -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) {