@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
@@ -60,7 +60,6 @@ export async function recordNoActionClosure(db, cycleId, noActionReason, options
60
60
  },
61
61
  ],
62
62
  redactionClass: "none",
63
- lifecycleStatus: "closed",
64
63
  payloadJson: JSON.stringify({ dispatchAttempt: 0, inputSummary: "no-action" }),
65
64
  });
66
65
  if ("reason" in result) {
@@ -78,12 +77,13 @@ export async function recordRememberClosure(db, cycleId, memoryReviewCandidate,
78
77
  id: closureId,
79
78
  createdAt: now,
80
79
  cycleId,
80
+ platformId: options?.platformId ?? "heartbeat",
81
+ capabilityId: options?.capabilityId,
81
82
  status: "completed",
82
83
  reason: "remember_for_review",
83
84
  nextState: "pending_daily_review",
84
85
  sourceRefs: memoryReviewCandidate.sourceRefs,
85
86
  redactionClass: "none",
86
- lifecycleStatus: "closed",
87
87
  payloadJson: JSON.stringify({
88
88
  memoryReviewCandidate,
89
89
  dispatchAttempt: 1,
@@ -132,7 +132,6 @@ export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, rea
132
132
  nextState: params.nextState ?? "await_next_cycle",
133
133
  sourceRefs,
134
134
  redactionClass: "none",
135
- lifecycleStatus: "closed",
136
135
  payloadJson: JSON.stringify({
137
136
  dispatchAttempt: 1,
138
137
  inputSummary: buildInputSummary(params.proposalId, params.decisionId),
@@ -173,7 +172,6 @@ export async function recordExecutionClosure(db, cycleId, closureStatus, reason,
173
172
  nextState: params.nextState ?? (closureStatus === "completed" ? "await_next_cycle" : "retryable"),
174
173
  sourceRefs,
175
174
  redactionClass: "none",
176
- lifecycleStatus: "closed",
177
175
  payloadJson: JSON.stringify({
178
176
  dispatchAttempt: 1,
179
177
  executionResultRef: params.executionResultRef,
@@ -22,22 +22,12 @@
22
22
  *
23
23
  * Test coverage: tests/unit/action/action-proposal-builder.test.ts
24
24
  */
25
- import { readJudgmentVerdictById, writeActionClosureRecord, } from "../../../storage/v8-state-stores.js";
25
+ import { readJudgmentVerdictById, } from "../../../storage/v8-state-stores.js";
26
+ import { parseSourceRefs } from "../../../shared/serialization.js";
26
27
  import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
27
28
  // ───────────────────────────────────────────────────────────────
28
29
  // Helpers
29
30
  // ───────────────────────────────────────────────────────────────
30
- function parseVerdictSourceRefs(json) {
31
- if (!json)
32
- return [];
33
- try {
34
- const parsed = JSON.parse(json);
35
- return Array.isArray(parsed) ? parsed : [];
36
- }
37
- catch {
38
- return [];
39
- }
40
- }
41
31
  function buildExpectedOutput(actionKind) {
42
32
  switch (actionKind) {
43
33
  case "ignore":
@@ -99,8 +89,8 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
99
89
  judgmentVerdictId,
100
90
  };
101
91
  }
102
- const sourceRefs = parseVerdictSourceRefs(verdict.sourceRefsJson);
103
- // remember → memory review candidate closure (no direct projection)
92
+ const sourceRefs = parseSourceRefs(verdict.sourceRefsJson);
93
+ // remember → memory review candidate (no direct projection; orchestrator writes closure)
104
94
  if (actionKind === "remember") {
105
95
  const candidate = {
106
96
  closureSubtype: "remember_for_review",
@@ -131,27 +121,10 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
131
121
  },
132
122
  ]),
133
123
  };
134
- const closureId = `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`;
135
- const writeResult = await writeActionClosureRecord(db, {
136
- id: closureId,
137
- createdAt: now,
138
- cycleId,
139
- platformId: "heartbeat",
140
- status: "completed",
141
- reason: "remember_for_review",
142
- nextState: "pending_daily_review",
143
- sourceRefs: candidate.sourceRefs,
144
- redactionClass: "none",
145
- lifecycleStatus: "closed",
146
- payloadJson: JSON.stringify({ memoryReviewCandidate: candidate }),
147
- });
148
- if ("reason" in writeResult) {
149
- return writeResult;
150
- }
151
124
  return {
152
125
  status: "remember_for_review",
153
126
  memoryReviewCandidate: candidate,
154
- closureId,
127
+ closureId: `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`,
155
128
  };
156
129
  }
157
130
  // Actionable verdict → build proposal
@@ -20,6 +20,7 @@
20
20
  *
21
21
  * Test coverage: tests/unit/action/policy-bound-dispatch.test.ts
22
22
  */
23
+ import { serializeSourceRefs } from "../../../shared/serialization.js";
23
24
  // ───────────────────────────────────────────────────────────────
24
25
  // Helpers
25
26
  // ───────────────────────────────────────────────────────────────
@@ -60,7 +61,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
60
61
  actionKind: target,
61
62
  draftType,
62
63
  policyProof: { decisionId: decision.id, decision: decision.decision },
63
- sourceRefs: JSON.stringify(decision.proofRefs),
64
+ sourceRefs: serializeSourceRefs(decision.proofRefs),
64
65
  },
65
66
  };
66
67
  }
@@ -75,7 +76,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
75
76
  capabilityId: proposal.targetCapabilityId ?? "run_connector",
76
77
  idempotencyKey: proposal.idempotencyKey,
77
78
  policyProof: { decisionId: decision.id, decision: decision.decision },
78
- sourceRefs: JSON.stringify(proposal.sourceRefs),
79
+ sourceRefs: serializeSourceRefs(proposal.sourceRefs),
79
80
  },
80
81
  };
81
82
  }
@@ -91,7 +92,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
91
92
  actionKind: proposal.actionKind,
92
93
  draftType,
93
94
  policyProof: { decisionId: decision.id, decision: decision.decision },
94
- sourceRefs: JSON.stringify(proposal.sourceRefs),
95
+ sourceRefs: serializeSourceRefs(proposal.sourceRefs),
95
96
  },
96
97
  };
97
98
  }
@@ -20,6 +20,7 @@
20
20
  * Test coverage: tests/unit/control-plane/accepted-projection-loader.test.ts
21
21
  */
22
22
  import { readMemoryProjectionsByStatus, } from "../../../storage/v8-state-stores.js";
23
+ import { parseSourceRefs } from "../../../shared/serialization.js";
23
24
  // ───────────────────────────────────────────────────────────────
24
25
  // Helpers
25
26
  // ───────────────────────────────────────────────────────────────
@@ -33,17 +34,6 @@ function parsePayloadJson(json) {
33
34
  return {};
34
35
  }
35
36
  }
36
- function parseSourceRefs(json) {
37
- if (!json)
38
- return [];
39
- try {
40
- const parsed = JSON.parse(json);
41
- return Array.isArray(parsed) ? parsed : [];
42
- }
43
- catch {
44
- return [];
45
- }
46
- }
47
37
  // ───────────────────────────────────────────────────────────────
48
38
  // Public API
49
39
  // ───────────────────────────────────────────────────────────────
@@ -35,6 +35,7 @@ export interface HeartbeatOrchestrationResult {
35
35
  closureRef?: SourceRef;
36
36
  noActionReason?: V8ReasonCode;
37
37
  degraded?: DegradedOperationResult;
38
+ rhythmDegraded?: DegradedOperationResult;
38
39
  rhythmState?: DailyRhythmState;
39
40
  }
40
41
  export declare function runHeartbeatCycle(db: StateDatabase, request: HeartbeatOrchestrationRequest): Promise<HeartbeatOrchestrationResult | DegradedOperationResult>;
@@ -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
  }));