@haaaiawd/second-nature 0.1.8 → 0.1.9

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 (183) hide show
  1. package/index.js +73 -1
  2. package/openclaw.plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/runtime/cli/commands/index.d.ts +2 -0
  5. package/runtime/cli/commands/index.js +61 -6
  6. package/runtime/cli/explain/explain-surface-subject.d.ts +8 -0
  7. package/runtime/cli/explain/explain-surface-subject.js +9 -0
  8. package/runtime/cli/explain/format-explanation.d.ts +2 -0
  9. package/runtime/cli/explain/format-explanation.js +2 -0
  10. package/runtime/cli/explain/resolve-subject.js +15 -0
  11. package/runtime/cli/host-capability/classify-delivery.d.ts +14 -0
  12. package/runtime/cli/host-capability/classify-delivery.js +20 -0
  13. package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -0
  14. package/runtime/cli/host-capability/probe-host-capability.js +58 -0
  15. package/runtime/cli/host-capability/record-host-capability.d.ts +6 -0
  16. package/runtime/cli/host-capability/record-host-capability.js +14 -0
  17. package/runtime/cli/host-capability/types.d.ts +71 -0
  18. package/runtime/cli/host-capability/types.js +6 -0
  19. package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -0
  20. package/runtime/cli/host-smoke/run-host-smoke.js +40 -0
  21. package/runtime/cli/host-smoke/types.d.ts +35 -0
  22. package/runtime/cli/host-smoke/types.js +6 -0
  23. package/runtime/cli/index.js +18 -0
  24. package/runtime/cli/ops/heartbeat-surface.d.ts +35 -0
  25. package/runtime/cli/ops/heartbeat-surface.js +71 -0
  26. package/runtime/cli/ops/ops-router.d.ts +16 -0
  27. package/runtime/cli/ops/ops-router.js +83 -0
  28. package/runtime/cli/ops/show-operator-fallback.d.ts +13 -0
  29. package/runtime/cli/ops/show-operator-fallback.js +22 -0
  30. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +10 -0
  31. package/runtime/cli/ops/workspace-heartbeat-runner.js +26 -0
  32. package/runtime/cli/read-models/index.d.ts +11 -2
  33. package/runtime/cli/read-models/index.js +50 -0
  34. package/runtime/cli/read-models/operator-explain-map.d.ts +6 -0
  35. package/runtime/cli/read-models/operator-explain-map.js +10 -0
  36. package/runtime/cli/read-models/types.d.ts +5 -1
  37. package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -0
  38. package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -0
  39. package/runtime/connectors/base/contract.d.ts +6 -0
  40. package/runtime/connectors/base/execution-policy.d.ts +47 -0
  41. package/runtime/connectors/base/execution-policy.js +82 -0
  42. package/runtime/connectors/base/index.d.ts +2 -0
  43. package/runtime/connectors/base/index.js +2 -0
  44. package/runtime/connectors/base/manifest.d.ts +55 -2
  45. package/runtime/connectors/base/manifest.js +50 -0
  46. package/runtime/connectors/base/map-life-evidence.d.ts +16 -0
  47. package/runtime/connectors/base/map-life-evidence.js +79 -0
  48. package/runtime/connectors/base/policy-layer.d.ts +2 -0
  49. package/runtime/connectors/base/policy-layer.js +16 -0
  50. package/runtime/connectors/base/route-planner.js +1 -0
  51. package/runtime/connectors/index.d.ts +1 -0
  52. package/runtime/connectors/index.js +1 -0
  53. package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -0
  54. package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -0
  55. package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +2 -0
  56. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +37 -16
  57. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +95 -29
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -1
  59. package/runtime/core/second-nature/heartbeat/index.js +4 -1
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -0
  62. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -0
  63. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -0
  64. package/runtime/core/second-nature/heartbeat/signal.d.ts +9 -2
  65. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +19 -1
  66. package/runtime/core/second-nature/index.d.ts +8 -0
  67. package/runtime/core/second-nature/index.js +8 -0
  68. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +1 -1
  69. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +9 -4
  70. package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +6 -0
  71. package/runtime/core/second-nature/orchestrator/guard-layer.js +76 -20
  72. package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +10 -0
  73. package/runtime/core/second-nature/orchestrator/intent-planner.js +135 -28
  74. package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +1 -1
  75. package/runtime/core/second-nature/orchestrator/lease-manager.js +1 -1
  76. package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -0
  77. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -0
  78. package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -0
  79. package/runtime/core/second-nature/outreach/delivery-target.js +70 -0
  80. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -0
  81. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -0
  82. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -0
  83. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -0
  84. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -0
  85. package/runtime/core/second-nature/outreach/judge-outreach.js +121 -0
  86. package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -0
  87. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -0
  88. package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -0
  89. package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -0
  90. package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -0
  91. package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -0
  92. package/runtime/core/second-nature/types.d.ts +16 -2
  93. package/runtime/guidance/draft-outreach-message.d.ts +7 -0
  94. package/runtime/guidance/draft-outreach-message.js +42 -0
  95. package/runtime/guidance/evidence-guidance.d.ts +40 -0
  96. package/runtime/guidance/evidence-guidance.js +52 -0
  97. package/runtime/guidance/index.d.ts +3 -0
  98. package/runtime/guidance/index.js +3 -0
  99. package/runtime/guidance/outreach-draft-schema.d.ts +228 -0
  100. package/runtime/guidance/outreach-draft-schema.js +80 -0
  101. package/runtime/observability/audit/append-only-audit-store.d.ts +14 -0
  102. package/runtime/observability/audit/append-only-audit-store.js +21 -0
  103. package/runtime/observability/audit/audit-envelope.d.ts +51 -0
  104. package/runtime/observability/audit/audit-envelope.js +130 -0
  105. package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -0
  106. package/runtime/observability/audit/verify-audit-hash-chain.js +83 -0
  107. package/runtime/observability/db/index.js +11 -0
  108. package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -0
  109. package/runtime/observability/db/schema/host-capability-reports.js +12 -0
  110. package/runtime/observability/db/schema/index.d.ts +1 -0
  111. package/runtime/observability/db/schema/index.js +1 -0
  112. package/runtime/observability/index.d.ts +7 -0
  113. package/runtime/observability/index.js +7 -0
  114. package/runtime/observability/query/explain-query.d.ts +48 -0
  115. package/runtime/observability/query/explain-query.js +114 -0
  116. package/runtime/observability/query/export-audit-bundle.d.ts +22 -0
  117. package/runtime/observability/query/export-audit-bundle.js +27 -0
  118. package/runtime/observability/services/decision-ledger.d.ts +1 -1
  119. package/runtime/observability/services/decision-ledger.js +4 -0
  120. package/runtime/observability/services/governance-audit.d.ts +14 -0
  121. package/runtime/observability/services/governance-audit.js +25 -1
  122. package/runtime/observability/services/governance-plane-recorder.d.ts +47 -0
  123. package/runtime/observability/services/governance-plane-recorder.js +55 -0
  124. package/runtime/observability/services/lived-experience-audit.d.ts +97 -0
  125. package/runtime/observability/services/lived-experience-audit.js +161 -0
  126. package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -0
  127. package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -0
  128. package/runtime/storage/bootstrap/repair-gate.d.ts +17 -0
  129. package/runtime/storage/bootstrap/repair-gate.js +71 -0
  130. package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -0
  131. package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -0
  132. package/runtime/storage/db/index.js +49 -0
  133. package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -0
  134. package/runtime/storage/db/schema/delivery-attempts.js +13 -0
  135. package/runtime/storage/db/schema/index.d.ts +3 -0
  136. package/runtime/storage/db/schema/index.js +3 -0
  137. package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -0
  138. package/runtime/storage/db/schema/life-evidence-index.js +11 -0
  139. package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -0
  140. package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -0
  141. package/runtime/storage/db/schema/policies.d.ts +17 -0
  142. package/runtime/storage/db/schema/policies.js +1 -0
  143. package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -0
  144. package/runtime/storage/delivery/query-delivery-attempts.js +32 -0
  145. package/runtime/storage/delivery/types.d.ts +27 -0
  146. package/runtime/storage/delivery/types.js +1 -0
  147. package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -0
  148. package/runtime/storage/delivery/write-delivery-attempt.js +36 -0
  149. package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -0
  150. package/runtime/storage/fallback/load-operator-fallback.js +47 -0
  151. package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -0
  152. package/runtime/storage/fallback/operator-fallback-types.js +1 -0
  153. package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -0
  154. package/runtime/storage/fallback/operator-fallback-view.js +1 -0
  155. package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -0
  156. package/runtime/storage/fallback/write-operator-fallback.js +21 -0
  157. package/runtime/storage/index.d.ts +21 -0
  158. package/runtime/storage/index.js +14 -0
  159. package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -0
  160. package/runtime/storage/life-evidence/append-life-evidence.js +64 -0
  161. package/runtime/storage/life-evidence/types.d.ts +45 -0
  162. package/runtime/storage/life-evidence/types.js +6 -0
  163. package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -0
  164. package/runtime/storage/quiet/persist-quiet-artifact.js +22 -0
  165. package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -0
  166. package/runtime/storage/quiet/quiet-artifact-types.js +1 -0
  167. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -0
  168. package/runtime/storage/quiet/quiet-artifact-writer.js +56 -0
  169. package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -0
  170. package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -0
  171. package/runtime/storage/services/credential-vault.d.ts +5 -0
  172. package/runtime/storage/services/credential-vault.js +46 -9
  173. package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -0
  174. package/runtime/storage/snapshots/continuity-snapshot.js +41 -0
  175. package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -0
  176. package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -0
  177. package/runtime/storage/snapshots/types.d.ts +58 -0
  178. package/runtime/storage/snapshots/types.js +1 -0
  179. package/runtime/storage/state-api.js +11 -4
  180. package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -0
  181. package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -0
  182. package/runtime/storage/user-interest/types.d.ts +25 -0
  183. package/runtime/storage/user-interest/types.js +1 -0
@@ -1,69 +1,135 @@
1
1
  import { buildContinuitySnapshot } from "./snapshot-builder.js";
2
- import { planIntent } from "../orchestrator/intent-planner.js";
3
- import { evaluateGuards } from "../orchestrator/guard-layer.js";
2
+ import { buildHeartbeatRuntimeSnapshot } from "./runtime-snapshot.js";
3
+ import { planCandidateIntents } from "../orchestrator/intent-planner.js";
4
+ import { evaluateHardGuards } from "../orchestrator/guard-layer.js";
5
+ import { dispatchUserOutreachIntent } from "../outreach/dispatch-user-outreach.js";
6
+ import { buildJudgeOutreachInputFromSnapshot } from "../outreach/judge-input-from-snapshot.js";
7
+ import { runSourceBackedQuiet } from "../quiet/run-source-backed-quiet.js";
8
+ /**
9
+ * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
10
+ * Exported for unit tests (CR-M1 wiring).
11
+ */
12
+ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal, deps) {
13
+ const day = typeof signal.payload.timestamp === "string" ? signal.payload.timestamp.slice(0, 10) : "1970-01-01";
14
+ if (intent.effectClass === "user_outreach" && deps.outreachDispatch) {
15
+ return dispatchUserOutreachIntent({
16
+ candidate: intent,
17
+ snapshot: runtime,
18
+ judgeInput: buildJudgeOutreachInputFromSnapshot(intent, runtime, inputs),
19
+ guidance: deps.outreachDispatch.guidance,
20
+ delivery: deps.outreachDispatch.delivery,
21
+ state: deps.outreachDispatch.state,
22
+ });
23
+ }
24
+ if (deps.quietWorkflow &&
25
+ (intent.kind === "quiet" || (intent.kind === "reflection" && intent.effectClass === "narrative_reflection"))) {
26
+ const quietRun = await runSourceBackedQuiet({
27
+ candidate: intent,
28
+ runtime,
29
+ day,
30
+ userInterestSnapshot: inputs.userInterestSnapshot,
31
+ workspaceRoot: deps.quietWorkflow.workspaceRoot,
32
+ });
33
+ return quietRun.result;
34
+ }
35
+ return {
36
+ scope: "rhythm",
37
+ status: "intent_selected",
38
+ selectedIntentId: intent.id,
39
+ reasons: [],
40
+ };
41
+ }
4
42
  /**
5
43
  * Ingest a heartbeat rhythm signal and drive one full decision round.
6
- *
7
- * Decision flow:
8
- * 1. Build continuity snapshot from state-system inputs
9
- * 2. Plan candidate intents from snapshot
10
- * 3. Evaluate guards for each candidate in priority order
11
- * 4. Return one of:
12
- * - intent_selected: a candidate passed all guards
13
- * - denied: candidates existed but all were rejected by guards
14
- * - heartbeat_ok: no candidates or no action warranted (conservative default)
15
- *
16
- * Per ADR-005: heartbeat is the free-rhythm main entry; this loop
17
- * implements the default conservative path where HEARTBEAT_OK is
18
- * the first-class result when no action is warranted.
19
44
  */
20
45
  export async function ingestRhythmSignal(signal, deps) {
21
- // Step 1: Build continuity snapshot
22
46
  const inputs = await deps.loadSnapshotInputs();
23
47
  const snapshot = buildContinuitySnapshot(inputs);
24
- // Step 2: Plan candidate intents
25
- const candidates = planIntent(snapshot);
26
- // Step 3: Evaluate guards for each candidate (priority order)
48
+ const timestamp = signal.payload.timestamp;
49
+ const runtime = buildHeartbeatRuntimeSnapshot(timestamp, inputs, snapshot);
50
+ const candidates = planCandidateIntents(runtime);
51
+ const emitTrace = async (result) => {
52
+ if (!deps.recordDecisionTrace)
53
+ return;
54
+ await deps.recordDecisionTrace({
55
+ scope: result.scope,
56
+ status: result.status,
57
+ reasons: result.reasons,
58
+ selectedIntentId: result.selectedIntentId,
59
+ rhythmWindowId: runtime.rhythmWindow.windowId,
60
+ allowedIntentKinds: [...runtime.rhythmWindow.allowedIntentKinds],
61
+ candidateCount: candidates.length,
62
+ lifeEvidenceEmpty: runtime.lifeEvidence.evidenceRefs.length === 0 &&
63
+ runtime.lifeEvidence.platformEventCount === 0 &&
64
+ runtime.lifeEvidence.workEventCount === 0,
65
+ trigger: signal.trigger,
66
+ });
67
+ };
27
68
  let hasCandidates = false;
28
69
  let anyAllow = false;
70
+ let anyDefer = false;
71
+ let anyDeny = false;
29
72
  const denyReasons = [];
30
73
  for (const intent of candidates) {
31
74
  hasCandidates = true;
32
- const evaluation = evaluateGuards(intent, snapshot);
75
+ const evaluation = evaluateHardGuards(intent, runtime);
33
76
  if (evaluation.verdict === "allow") {
34
77
  anyAllow = true;
35
- return {
78
+ const base = {
36
79
  scope: "rhythm",
37
80
  status: "intent_selected",
38
81
  selectedIntentId: intent.id,
39
82
  reasons: evaluation.reasons,
40
83
  };
84
+ const resolved = await resolveAllowedIntentResult(intent, runtime, inputs, signal, deps);
85
+ const result = resolved.status === "intent_selected" && resolved.reasons.length === 0 && evaluation.reasons.length > 0
86
+ ? { ...resolved, reasons: evaluation.reasons }
87
+ : resolved;
88
+ await emitTrace(result);
89
+ return result;
41
90
  }
91
+ if (evaluation.verdict === "defer") {
92
+ anyDefer = true;
93
+ denyReasons.push(`${intent.id}:${evaluation.verdict}(${evaluation.reasons.join(",")})`);
94
+ continue;
95
+ }
96
+ anyDeny = true;
42
97
  denyReasons.push(`${intent.id}:${evaluation.verdict}(${evaluation.reasons.join(",")})`);
43
98
  }
44
- // Step 4: No viable intent path
45
99
  if (!hasCandidates) {
46
- // No candidates at all → heartbeat_ok (nothing to do)
47
- return {
100
+ const result = {
48
101
  scope: "rhythm",
49
102
  status: "heartbeat_ok",
50
- reasons: ["no_candidates"],
103
+ reasons: ["silent_no_candidates"],
51
104
  };
105
+ await emitTrace(result);
106
+ return result;
107
+ }
108
+ if (!anyAllow && anyDefer && !anyDeny) {
109
+ const result = {
110
+ scope: "rhythm",
111
+ status: "deferred",
112
+ reasons: denyReasons.length > 0 ? denyReasons : ["all_candidates_deferred"],
113
+ };
114
+ await emitTrace(result);
115
+ return result;
52
116
  }
53
117
  if (!anyAllow && denyReasons.length > 0) {
54
- // Candidates existed but all denied/deferred/escalated → denied
55
- return {
118
+ const result = {
56
119
  scope: "rhythm",
57
120
  status: "denied",
58
121
  reasons: denyReasons,
59
122
  };
123
+ await emitTrace(result);
124
+ return result;
60
125
  }
61
- // Fallback: conservative heartbeat_ok
62
- return {
126
+ const result = {
63
127
  scope: "rhythm",
64
128
  status: "heartbeat_ok",
65
129
  reasons: ["no_allow_verdict"],
66
130
  };
131
+ await emitTrace(result);
132
+ return result;
67
133
  }
68
134
  /**
69
135
  * Build a snapshot directly from inputs (for testing or when state-system is unavailable).
@@ -1,5 +1,8 @@
1
1
  export { type RuntimeScope, type RuntimeTrigger, type HeartbeatCycleStatus, type HeartbeatSignal, type ScopedRuntimeInput, type HeartbeatCycleResult, type ScopeRouteResult, } from "./signal.js";
2
2
  export { buildContinuitySnapshot, type SnapshotInputs, } from "./snapshot-builder.js";
3
- export { ingestRhythmSignal, type HeartbeatDeps, buildSnapshotFromInputs, } from "./heartbeat-loop.js";
3
+ export { ingestRhythmSignal, resolveAllowedIntentResult, type HeartbeatDeps, type HeartbeatOutreachDispatchDeps, type HeartbeatQuietWorkflowDeps, type HeartbeatDecisionTracePayload, buildSnapshotFromInputs, } from "./heartbeat-loop.js";
4
+ export { buildHeartbeatRuntimeSnapshot, buildLifeEvidenceSliceFromInputs, buildHardGuardDeps, resolveRhythmPolicyForHeartbeat, isLifeEvidenceSliceEmpty, type HeartbeatRuntimeSnapshot, type PlannerLifeEvidenceSlice, type HardGuardDeps, } from "./runtime-snapshot.js";
5
+ export { buildPlannerRhythmWindow, type PlannerRhythmWindowSlice } from "../rhythm/planner-rhythm-window.js";
6
+ export { runHeartbeatCycle, type RunHeartbeatCycleInput } from "./run-heartbeat-cycle.js";
4
7
  export { routeScopedInput, type ScopeRouterDeps, } from "./scope-router.js";
5
8
  export { requestGuidanceForIntent, dispatchAllowedEffect, executeHeartbeatCycle, type GuidanceBridgeDeps, type EffectDispatchDeps, type HeartbeatExecutorDeps, type GuidanceBridgeResult, type HeartbeatExecutionResult, } from "./heartbeat-executor.js";
@@ -1,4 +1,7 @@
1
1
  export { buildContinuitySnapshot, } from "./snapshot-builder.js";
2
- export { ingestRhythmSignal, buildSnapshotFromInputs, } from "./heartbeat-loop.js";
2
+ export { ingestRhythmSignal, resolveAllowedIntentResult, buildSnapshotFromInputs, } from "./heartbeat-loop.js";
3
+ export { buildHeartbeatRuntimeSnapshot, buildLifeEvidenceSliceFromInputs, buildHardGuardDeps, resolveRhythmPolicyForHeartbeat, isLifeEvidenceSliceEmpty, } from "./runtime-snapshot.js";
4
+ export { buildPlannerRhythmWindow } from "../rhythm/planner-rhythm-window.js";
5
+ export { runHeartbeatCycle } from "./run-heartbeat-cycle.js";
3
6
  export { routeScopedInput, } from "./scope-router.js";
4
7
  export { requestGuidanceForIntent, dispatchAllowedEffect, executeHeartbeatCycle, } from "./heartbeat-executor.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Control-plane heartbeat cycle entry (T2.1.1).
3
+ *
4
+ * Core logic: runtime availability gate → scope routing (user_task bypasses rhythm) →
5
+ * rhythm path delegates to ingestRhythmSignal. Mirrors L0 control-plane-system §4.3.
6
+ *
7
+ * Boundaries: does not claim lived-experience completion when runtime is unavailable;
8
+ * user_task / user_reply do not enter the rhythm candidate planner.
9
+ */
10
+ import type { HeartbeatSignal, HeartbeatCycleResult } from "./signal.js";
11
+ import type { HeartbeatDeps } from "./heartbeat-loop.js";
12
+ export interface RunHeartbeatCycleInput {
13
+ signal: HeartbeatSignal;
14
+ /** When false, return runtime_carrier_only without loading snapshots (host-safe carrier). */
15
+ runtimeAvailable: boolean;
16
+ deps: HeartbeatDeps;
17
+ }
18
+ /**
19
+ * Single entry for one heartbeat turn: scope routing, runtime gate, then rhythm loop if applicable.
20
+ */
21
+ export declare function runHeartbeatCycle(input: RunHeartbeatCycleInput): Promise<HeartbeatCycleResult>;
@@ -0,0 +1,35 @@
1
+ import { ingestRhythmSignal } from "./heartbeat-loop.js";
2
+ import { routeScopedInput } from "./scope-router.js";
3
+ /**
4
+ * Single entry for one heartbeat turn: scope routing, runtime gate, then rhythm loop if applicable.
5
+ */
6
+ export async function runHeartbeatCycle(input) {
7
+ const scoped = {
8
+ trigger: input.signal.trigger,
9
+ scopeHint: input.signal.scopeHint,
10
+ payload: input.signal.payload,
11
+ };
12
+ const route = routeScopedInput(scoped);
13
+ if (!input.runtimeAvailable) {
14
+ return {
15
+ scope: route.scope,
16
+ status: "runtime_carrier_only",
17
+ reasons: ["runtime_unavailable_no_lived_experience_loop"],
18
+ };
19
+ }
20
+ if (route.scope === "user_task") {
21
+ return {
22
+ scope: "user_task",
23
+ status: "heartbeat_ok",
24
+ reasons: ["rhythm_gate_bypass_user_task"],
25
+ };
26
+ }
27
+ if (route.scope === "user_reply") {
28
+ return {
29
+ scope: "user_reply",
30
+ status: "heartbeat_ok",
31
+ reasons: ["user_reply_light_continuity_skeleton"],
32
+ };
33
+ }
34
+ return ingestRhythmSignal(input.signal, input.deps);
35
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * HeartbeatRuntimeSnapshot assembly for candidate planner + hard guards (T2.1.3, T2.2.1).
3
+ */
4
+ import type { ContinuitySnapshot, ControlPlaneSourceRef } from "../types.js";
5
+ import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
6
+ import { type PlannerRhythmWindowSlice } from "../rhythm/planner-rhythm-window.js";
7
+ import type { SnapshotInputs } from "./snapshot-builder.js";
8
+ export interface PlannerLifeEvidenceSlice {
9
+ evidenceRefs: ControlPlaneSourceRef[];
10
+ platformEventCount: number;
11
+ workEventCount: number;
12
+ emptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
13
+ }
14
+ export declare function isLifeEvidenceSliceEmpty(slice: PlannerLifeEvidenceSlice): boolean;
15
+ export interface HardGuardDeps {
16
+ hasDuplicateIntent: (idempotencyKey: string) => boolean;
17
+ isOutreachCooldownClear: (idempotencyKey: string) => boolean;
18
+ }
19
+ export interface HeartbeatRuntimeSnapshot {
20
+ continuity: ContinuitySnapshot;
21
+ lifeEvidence: PlannerLifeEvidenceSlice;
22
+ rhythmWindow: PlannerRhythmWindowSlice;
23
+ hardGuards: HardGuardDeps;
24
+ }
25
+ export declare function buildLifeEvidenceSliceFromInputs(inputs: SnapshotInputs): PlannerLifeEvidenceSlice;
26
+ export declare function buildHardGuardDeps(continuity: ContinuitySnapshot, inputs: SnapshotInputs): HardGuardDeps;
27
+ export declare function resolveRhythmPolicyForHeartbeat(inputs: SnapshotInputs): RhythmPolicy;
28
+ export declare function buildHeartbeatRuntimeSnapshot(timestamp: string, inputs: SnapshotInputs, continuity: ContinuitySnapshot): HeartbeatRuntimeSnapshot;
@@ -0,0 +1,35 @@
1
+ import { rhythmPolicySnapshotToRhythmPolicy } from "../rhythm/policy-bridge.js";
2
+ import { buildPlannerRhythmWindow } from "../rhythm/planner-rhythm-window.js";
3
+ export function isLifeEvidenceSliceEmpty(slice) {
4
+ return slice.evidenceRefs.length === 0 && slice.platformEventCount === 0 && slice.workEventCount === 0;
5
+ }
6
+ export function buildLifeEvidenceSliceFromInputs(inputs) {
7
+ return {
8
+ evidenceRefs: inputs.lifeEvidenceRefs ?? [],
9
+ platformEventCount: inputs.platformEventCount ?? 0,
10
+ workEventCount: inputs.workEventCount ?? 0,
11
+ emptyReason: inputs.lifeEvidenceEmptyReason,
12
+ };
13
+ }
14
+ export function buildHardGuardDeps(continuity, inputs) {
15
+ return {
16
+ hasDuplicateIntent: (key) => (inputs.duplicateIntentKeys?.includes(key) ?? false) ||
17
+ continuity.deniedIntents.some((d) => d.reason === "duplicate_intent" && d.intentHash === key),
18
+ isOutreachCooldownClear: (key) => !(inputs.outreachCooldownKeys?.includes(key) ?? false),
19
+ };
20
+ }
21
+ export function resolveRhythmPolicyForHeartbeat(inputs) {
22
+ if (inputs.rhythmPolicy) {
23
+ return inputs.rhythmPolicy;
24
+ }
25
+ return rhythmPolicySnapshotToRhythmPolicy({
26
+ quietEnabled: inputs.quietEnabledBridge ?? false,
27
+ });
28
+ }
29
+ export function buildHeartbeatRuntimeSnapshot(timestamp, inputs, continuity) {
30
+ const policy = resolveRhythmPolicyForHeartbeat(inputs);
31
+ const rhythmWindow = buildPlannerRhythmWindow(timestamp, continuity, policy);
32
+ const lifeEvidence = buildLifeEvidenceSliceFromInputs(inputs);
33
+ const hardGuards = buildHardGuardDeps(continuity, inputs);
34
+ return { continuity, lifeEvidence, rhythmWindow, hardGuards };
35
+ }
@@ -7,7 +7,11 @@
7
7
  */
8
8
  export type RuntimeScope = "rhythm" | "user_task" | "user_reply";
9
9
  export type RuntimeTrigger = "heartbeat_bridge" | "user_task" | "user_reply" | "interrupt" | "resume";
10
- export type HeartbeatCycleStatus = "heartbeat_ok" | "intent_selected" | "deferred" | "denied";
10
+ export type HeartbeatCycleStatus = "heartbeat_ok" | "intent_selected" | "deferred" | "denied"
11
+ /** Delivery port returned failed / host dropped; operator fallback written (T2.3.2 / ADR-007). */
12
+ | "delivery_unavailable"
13
+ /** Host-safe packaged carrier: no lived-experience loop (ADR-005 / control-plane L0). */
14
+ | "runtime_carrier_only";
11
15
  export interface HeartbeatSignal {
12
16
  trigger: RuntimeTrigger;
13
17
  scopeHint?: RuntimeScope;
@@ -23,10 +27,13 @@ export interface ScopedRuntimeInput {
23
27
  payload: Record<string, unknown>;
24
28
  }
25
29
  export interface HeartbeatCycleResult {
26
- scope: "rhythm";
30
+ scope: RuntimeScope;
27
31
  status: HeartbeatCycleStatus;
28
32
  selectedIntentId?: string;
29
33
  reasons: string[];
34
+ decisionId?: string;
35
+ deliveryAttemptId?: string;
36
+ fallbackRef?: string;
30
37
  }
31
38
  export interface ScopeRouteResult {
32
39
  scope: RuntimeScope;
@@ -6,7 +6,10 @@
6
6
  *
7
7
  * Per design doc §4.2: SnapshotBuilder prepares inputs for the Rhythm Engine.
8
8
  */
9
- import type { ContinuitySnapshot, TopLevelMode } from "../types.js";
9
+ import type { ContinuitySnapshot, ControlPlaneSourceRef, TopLevelMode } from "../types.js";
10
+ import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
11
+ import type { DeliveryCapabilitySnapshot } from "../outreach/delivery-target.js";
12
+ import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
10
13
  export interface SnapshotInputs {
11
14
  mode: TopLevelMode;
12
15
  currentWindowId: string;
@@ -23,6 +26,21 @@ export interface SnapshotInputs {
23
26
  };
24
27
  awaitingUserInput?: boolean;
25
28
  riskSuppressed?: boolean;
29
+ /** Evidence refs for source-backed planner/guards (T2.1.3 / T2.2.1). */
30
+ lifeEvidenceRefs?: ControlPlaneSourceRef[];
31
+ platformEventCount?: number;
32
+ workEventCount?: number;
33
+ lifeEvidenceEmptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
34
+ /** Optional explicit rhythm geometry; otherwise `quietEnabledBridge` drives policy-bridge default. */
35
+ rhythmPolicy?: RhythmPolicy;
36
+ /** Passed to `rhythmPolicySnapshotToRhythmPolicy` when `rhythmPolicy` is absent. */
37
+ quietEnabledBridge?: boolean;
38
+ duplicateIntentKeys?: string[];
39
+ outreachCooldownKeys?: string[];
40
+ /** When present, outreach judgment uses this delivery snapshot (ADR-007). */
41
+ deliveryCapability?: DeliveryCapabilitySnapshot;
42
+ /** When present, outreach judgment uses this user-interest read model (T4.2.2). */
43
+ userInterestSnapshot?: UserInterestSnapshot;
26
44
  }
27
45
  /**
28
46
  * Build a ContinuitySnapshot from loaded inputs.
@@ -1,8 +1,10 @@
1
1
  export * from "./types.js";
2
2
  export * from "./rhythm/rhythm-policy.js";
3
3
  export * from "./rhythm/select-window.js";
4
+ export * from "./rhythm/policy-bridge.js";
4
5
  export * from "./orchestrator/intent-planner.js";
5
6
  export * from "./orchestrator/guard-layer.js";
7
+ export * from "./heartbeat/runtime-snapshot.js";
6
8
  export * from "./orchestrator/lease-manager.js";
7
9
  export * from "./orchestrator/effect-dispatcher.js";
8
10
  export * from "./orchestrator/resume-from-checkpoint.js";
@@ -10,5 +12,11 @@ export * from "./quiet/quiet-pipeline.js";
10
12
  export * from "./reflection/run-narrative-reflection.js";
11
13
  export * from "./outreach/evaluate-outreach.js";
12
14
  export * from "./outreach/build-message.js";
15
+ export * from "./outreach/delivery-target.js";
16
+ export * from "./outreach/judge-outreach.js";
17
+ export * from "./outreach/build-outreach-draft-request.js";
18
+ export * from "./outreach/dispatch-user-outreach.js";
19
+ export * from "./outreach/judge-input-from-snapshot.js";
20
+ export * from "./quiet/run-source-backed-quiet.js";
13
21
  export * from "./guidance/request-guidance.js";
14
22
  export * from "./guidance/apply-guidance.js";
@@ -1,8 +1,10 @@
1
1
  export * from "./types.js";
2
2
  export * from "./rhythm/rhythm-policy.js";
3
3
  export * from "./rhythm/select-window.js";
4
+ export * from "./rhythm/policy-bridge.js";
4
5
  export * from "./orchestrator/intent-planner.js";
5
6
  export * from "./orchestrator/guard-layer.js";
7
+ export * from "./heartbeat/runtime-snapshot.js";
6
8
  export * from "./orchestrator/lease-manager.js";
7
9
  export * from "./orchestrator/effect-dispatcher.js";
8
10
  export * from "./orchestrator/resume-from-checkpoint.js";
@@ -10,5 +12,11 @@ export * from "./quiet/quiet-pipeline.js";
10
12
  export * from "./reflection/run-narrative-reflection.js";
11
13
  export * from "./outreach/evaluate-outreach.js";
12
14
  export * from "./outreach/build-message.js";
15
+ export * from "./outreach/delivery-target.js";
16
+ export * from "./outreach/judge-outreach.js";
17
+ export * from "./outreach/build-outreach-draft-request.js";
18
+ export * from "./outreach/dispatch-user-outreach.js";
19
+ export * from "./outreach/judge-input-from-snapshot.js";
20
+ export * from "./quiet/run-source-backed-quiet.js";
13
21
  export * from "./guidance/request-guidance.js";
14
22
  export * from "./guidance/apply-guidance.js";
@@ -2,7 +2,7 @@ import type { ConnectorResult, CapabilityIntent } from "../../../connectors/base
2
2
  import { LeaseManager, type EffectClass } from "./lease-manager.js";
3
3
  export interface AllowedIntent {
4
4
  id: string;
5
- kind: "work" | "exploration" | "social" | "reflection" | "outreach" | "maintenance";
5
+ kind: "work" | "exploration" | "social" | "quiet" | "reflection" | "outreach" | "maintenance";
6
6
  summary: string;
7
7
  effectClass: EffectClass;
8
8
  platformId?: string;
@@ -1,9 +1,12 @@
1
1
  import * as crypto from "crypto";
2
2
  function needsLease(effectClass) {
3
- return effectClass === "external_platform_action" || effectClass === "user_outreach";
3
+ return effectClass === "external_platform_action" || effectClass === "connector_action" || effectClass === "user_outreach";
4
4
  }
5
5
  function needsCheckpoint(effectClass) {
6
- return effectClass !== "maintenance";
6
+ return effectClass !== "maintenance" && effectClass !== "no_effect";
7
+ }
8
+ function isConnectorEffect(effectClass) {
9
+ return effectClass === "external_platform_action" || effectClass === "connector_action";
7
10
  }
8
11
  function toCapabilityIntent(intent) {
9
12
  if (intent.kind === "work")
@@ -14,6 +17,8 @@ function toCapabilityIntent(intent) {
14
17
  return "comment.reply";
15
18
  if (intent.kind === "outreach")
16
19
  return "message.send";
20
+ if (intent.kind === "quiet")
21
+ return "feed.read";
17
22
  return "feed.read";
18
23
  }
19
24
  export class EffectDispatcher {
@@ -43,7 +48,7 @@ export class EffectDispatcher {
43
48
  id: decision.checkpointId,
44
49
  tickId: decision.tickId,
45
50
  intentId: decision.intentId,
46
- phase: intent.effectClass === "external_platform_action" ? "before_effect" : "before_quiet_write",
51
+ phase: isConnectorEffect(intent.effectClass) ? "before_effect" : "before_quiet_write",
47
52
  snapshotRef: decision.traceId,
48
53
  });
49
54
  }
@@ -54,7 +59,7 @@ export class EffectDispatcher {
54
59
  state: "planned",
55
60
  });
56
61
  try {
57
- if (intent.effectClass === "external_platform_action") {
62
+ if (isConnectorEffect(intent.effectClass)) {
58
63
  await this.commitPort.advanceIntentCommitState(commit.id, "dispatched");
59
64
  const result = await this.connectorExecutor.executeEffect({
60
65
  platformId: intent.platformId ?? "unknown",
@@ -1,2 +1,8 @@
1
1
  import type { CandidateIntent, ContinuitySnapshot, GuardEvaluation } from "../types.js";
2
+ import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
3
+ /**
4
+ * Hard guard evaluation (T2.1.3): source, dedupe, cooldown, quiet bias, budget, risk, awaiting user.
5
+ */
6
+ export declare function evaluateHardGuards(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot): GuardEvaluation;
7
+ /** Continuity-only guard path for legacy call sites; builds a minimal runtime snapshot. */
2
8
  export declare function evaluateGuards(intent: CandidateIntent, snapshot: ContinuitySnapshot): GuardEvaluation;
@@ -1,9 +1,7 @@
1
- function stableIntentHash(intent) {
2
- return `${intent.kind}:${intent.summary}`;
3
- }
4
- function isDuplicateIntent(intent, snapshot) {
5
- const hash = stableIntentHash(intent);
6
- return snapshot.deniedIntents.some((item) => item.intentHash === hash && item.reason === "duplicate_intent");
1
+ import { buildHeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
2
+ const QUIET_DENY_KINDS = ["outreach", "social"];
3
+ function intentFingerprint(intent) {
4
+ return intent.idempotencyKey ?? `${intent.kind}:${intent.summary}`;
7
5
  }
8
6
  function isBudgetExceeded(intent, snapshot) {
9
7
  if (intent.kind !== "social")
@@ -12,43 +10,101 @@ function isBudgetExceeded(intent, snapshot) {
12
10
  return false;
13
11
  return snapshot.budgets.socialUsed >= snapshot.budgets.socialLimit;
14
12
  }
15
- function isQuietSuppressed(intent, snapshot) {
16
- if (snapshot.mode !== "quiet")
17
- return false;
18
- if (intent.effectClass === "maintenance" || intent.effectClass === "memory_curation" || intent.effectClass === "narrative_reflection") {
13
+ function isQuietSuppressed(intent, runtime) {
14
+ if (!runtime.rhythmWindow.quietBias)
19
15
  return false;
16
+ if (QUIET_DENY_KINDS.includes(intent.kind)) {
17
+ return true;
18
+ }
19
+ if (intent.effectClass === "connector_action" || intent.effectClass === "external_platform_action") {
20
+ return true;
20
21
  }
21
- return true;
22
+ return false;
22
23
  }
23
- export function evaluateGuards(intent, snapshot) {
24
+ function isSourceBacked(intent) {
25
+ if (intent.sourceRefs.length > 0)
26
+ return true;
27
+ if (intent.effectClass === "maintenance" || intent.effectClass === "no_effect")
28
+ return true;
29
+ return false;
30
+ }
31
+ function isRiskBlocked(intent, snapshot) {
32
+ if (!snapshot.riskSuppressed)
33
+ return false;
34
+ return intent.kind === "exploration" || intent.kind === "social" || intent.kind === "outreach";
35
+ }
36
+ /**
37
+ * Hard guard evaluation (T2.1.3): source, dedupe, cooldown, quiet bias, budget, risk, awaiting user.
38
+ */
39
+ export function evaluateHardGuards(intent, runtime) {
40
+ const snapshot = runtime.continuity;
24
41
  const reasons = [];
25
- if (isDuplicateIntent(intent, snapshot)) {
42
+ if (!isSourceBacked(intent)) {
43
+ reasons.push("missing_source_refs");
44
+ }
45
+ const key = intentFingerprint(intent);
46
+ if (runtime.hardGuards.hasDuplicateIntent(key)) {
26
47
  reasons.push("duplicate_intent");
27
48
  }
49
+ if (intent.effectClass === "user_outreach" && !runtime.hardGuards.isOutreachCooldownClear(key)) {
50
+ reasons.push("outreach_cooldown");
51
+ }
52
+ if (isQuietSuppressed(intent, runtime)) {
53
+ reasons.push("quiet_window_suppression");
54
+ }
28
55
  if (isBudgetExceeded(intent, snapshot)) {
29
56
  reasons.push("budget_exceeded");
30
57
  }
31
- if (isQuietSuppressed(intent, snapshot)) {
32
- reasons.push("quiet_window");
33
- }
34
58
  if (snapshot.awaitingUserInput) {
35
59
  reasons.push("awaiting_user");
36
60
  }
61
+ if (isRiskBlocked(intent, snapshot)) {
62
+ reasons.push("risk_suppressed");
63
+ }
37
64
  if (reasons.length === 0) {
38
65
  return {
39
66
  verdict: "allow",
40
- reasons,
67
+ reasons: [],
41
68
  quietSuppressed: false,
42
- leaseRequired: intent.effectClass === "external_platform_action" || intent.effectClass === "user_outreach",
43
- requiresCheckpoint: intent.effectClass !== "maintenance",
69
+ leaseRequired: intent.effectClass === "external_platform_action" ||
70
+ intent.effectClass === "connector_action" ||
71
+ intent.effectClass === "user_outreach",
72
+ requiresCheckpoint: intent.effectClass !== "maintenance" && intent.effectClass !== "no_effect",
73
+ };
74
+ }
75
+ const duplicate = reasons.includes("duplicate_intent");
76
+ const cooldown = reasons.includes("outreach_cooldown");
77
+ if (duplicate || cooldown) {
78
+ return {
79
+ verdict: "defer",
80
+ reasons,
81
+ quietSuppressed: reasons.includes("quiet_window_suppression"),
82
+ leaseRequired: false,
83
+ requiresCheckpoint: false,
44
84
  };
45
85
  }
46
86
  const escalated = reasons.includes("awaiting_user") && intent.kind === "outreach";
47
87
  return {
48
88
  verdict: escalated ? "escalate" : "deny",
49
89
  reasons,
50
- quietSuppressed: reasons.includes("quiet_window"),
90
+ quietSuppressed: reasons.includes("quiet_window_suppression"),
51
91
  leaseRequired: false,
52
92
  requiresCheckpoint: false,
53
93
  };
54
94
  }
95
+ /** Continuity-only guard path for legacy call sites; builds a minimal runtime snapshot. */
96
+ export function evaluateGuards(intent, snapshot) {
97
+ const inputs = {
98
+ mode: snapshot.mode,
99
+ currentWindowId: snapshot.currentWindowId,
100
+ pendingObligations: snapshot.pendingObligations,
101
+ recentOutreachHashes: snapshot.recentOutreachHashes,
102
+ deniedIntents: snapshot.deniedIntents,
103
+ budgets: snapshot.budgets,
104
+ awaitingUserInput: snapshot.awaitingUserInput,
105
+ riskSuppressed: snapshot.riskSuppressed,
106
+ quietEnabledBridge: snapshot.mode === "quiet",
107
+ };
108
+ const runtime = buildHeartbeatRuntimeSnapshot("2026-03-25T12:00:00.000Z", inputs, snapshot);
109
+ return evaluateHardGuards(intent, runtime);
110
+ }
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Candidate intent planner (T2.1.3): window-biased planning + priority cap.
3
+ * `planCandidateIntents` is the contract name; `planIntent` bridges legacy continuity-only tests.
4
+ */
1
5
  import type { CandidateIntent, ContinuitySnapshot, DecisionBasis } from "../types.js";
6
+ import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
7
+ /**
8
+ * Plan ordered candidates for one heartbeat turn using rhythm window + life evidence slice.
9
+ */
10
+ export declare function planCandidateIntents(runtime: HeartbeatRuntimeSnapshot): CandidateIntent[];
11
+ /** @deprecated Continuity-only helper for tests; prefer `planCandidateIntents` + `buildHeartbeatRuntimeSnapshot`. */
2
12
  export declare function planIntent(snapshot: ContinuitySnapshot): CandidateIntent[];
3
13
  export declare function decideDecisionBasis(intent: CandidateIntent): DecisionBasis;