@haaaiawd/second-nature 0.1.27 → 0.1.29

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 (157) hide show
  1. package/SKILL.md +35 -33
  2. package/agent-inner-guide.md +144 -124
  3. package/index.js +76 -1
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +2 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +67 -77
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  138. package/runtime/storage/services/diary-dream-store.js +165 -0
  139. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  140. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  141. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  142. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  143. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  144. package/runtime/storage/services/history-digest-store.js +140 -0
  145. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  146. package/runtime/storage/services/identity-profile-store.js +81 -0
  147. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  149. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  150. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  151. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  153. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  154. package/runtime/storage/services/tool-experience-store.js +116 -0
  155. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  156. package/runtime/storage/services/write-validation-gate.js +200 -0
  157. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * EmbodiedContextAssembler — T-CP.C.1
3
+ *
4
+ * Core logic: Assembles a complete EmbodiedContext from up to 7 read ports:
5
+ * - 5 state-memory slices via EmbodiedContextStatePort
6
+ * - affordanceMap via AffordanceAssembler
7
+ * - selfHealth via SelfHealthProvider (observability hook)
8
+ *
9
+ * Trim policies (DR-020):
10
+ * - recentInteractions: LIFO 10
11
+ * - toolExperience: LIFO 10
12
+ * - sourceRefs: deduplicated to 20 per slice
13
+ *
14
+ * Performance: P95 < 400ms for full assembly (DR-016).
15
+ *
16
+ * Dependencies:
17
+ * - `EmbodiedContextStatePort` from `../../../storage/services/embodied-context-state-port.js`
18
+ * - `AffordanceAssembler` from `../body/tool-affordance/affordance-assembler.js`
19
+ * - `EmbodiedContext` from `../../../shared/types/v7-entities.js`
20
+ *
21
+ * Boundary:
22
+ * - Candidate dream outputs are NOT included (DR-011).
23
+ * - Each slice gets its own loaded/degraded/blocked status.
24
+ * - Does NOT throw on partial failure; assembles best-effort context.
25
+ *
26
+ * Test coverage: tests/unit/control-plane/embodied-context-assembler.test.ts
27
+ */
28
+ function trimLifo(arr, limit) {
29
+ if (arr.length <= limit)
30
+ return arr;
31
+ return arr.slice(-limit);
32
+ }
33
+ function dedupSourceRefs(items, limit) {
34
+ const seen = new Set();
35
+ const result = [];
36
+ for (const item of items) {
37
+ if (item.sourceRefs && item.sourceRefs.length > 0) {
38
+ const key = item.sourceRefs.join("|");
39
+ if (seen.has(key))
40
+ continue;
41
+ if (seen.size >= limit)
42
+ break;
43
+ seen.add(key);
44
+ }
45
+ result.push(item);
46
+ }
47
+ return result;
48
+ }
49
+ export function createEmbodiedContextAssembler(deps) {
50
+ const { statePort, affordanceAssembler, selfHealthProvider, options = {}, } = deps;
51
+ const interactionLimit = options.interactionLimit ?? 10;
52
+ const experienceLimit = options.experienceLimit ?? 10;
53
+ const profileId = options.profileId ?? "default";
54
+ return {
55
+ async assembleEmbodiedContext() {
56
+ const assembledAt = new Date().toISOString();
57
+ // ── 1. Identity ───────────────────────────────────────────
58
+ const identityResult = await statePort.loadIdentityProfile(profileId);
59
+ const identitySlice = identityResult.status === "loaded"
60
+ ? { status: "loaded", data: identityResult.data }
61
+ : identityResult.status === "degraded" && identityResult.data
62
+ ? {
63
+ status: "degraded",
64
+ data: identityResult.data,
65
+ reason: identityResult.reason,
66
+ }
67
+ : {
68
+ status: "degraded",
69
+ data: {},
70
+ reason: identityResult.reason,
71
+ };
72
+ // ── 2. Goals ──────────────────────────────────────────────
73
+ const goalsResult = await statePort.listActiveGoals(10);
74
+ const goalsSlice = goalsResult.status === "loaded"
75
+ ? { status: "loaded", data: goalsResult.data }
76
+ : { status: "degraded", data: [], reason: goalsResult.reason };
77
+ // ── 3. Recent Interactions (trim LIFO) ────────────────────
78
+ const interactionResult = await statePort.loadRecentInteractionSnapshot(interactionLimit);
79
+ const recentInteractionsSlice = interactionResult.status === "loaded"
80
+ ? {
81
+ status: "loaded",
82
+ data: trimLifo(interactionResult.data, interactionLimit),
83
+ }
84
+ : {
85
+ status: "degraded",
86
+ data: [],
87
+ reason: interactionResult.reason,
88
+ };
89
+ // ── 4. Tool Experience (trim LIFO) ────────────────────────
90
+ const experienceResult = await statePort.loadToolExperienceSlice(experienceLimit);
91
+ const toolExperienceSlice = experienceResult.status === "loaded"
92
+ ? {
93
+ status: "loaded",
94
+ data: trimLifo(experienceResult.data, experienceLimit),
95
+ }
96
+ : {
97
+ status: "degraded",
98
+ data: [],
99
+ reason: experienceResult.reason,
100
+ };
101
+ // ── 5. Accepted Dream ─────────────────────────────────────
102
+ const dreamResult = await statePort.loadAcceptedDreamProjection(3);
103
+ const acceptedDreamSlice = dreamResult.status === "loaded"
104
+ ? { status: "loaded", data: dreamResult.data }
105
+ : { status: "degraded", data: [], reason: dreamResult.reason };
106
+ // ── 6. Affordance Map ─────────────────────────────────────
107
+ let affordanceMapSlice;
108
+ try {
109
+ const affordanceMap = await affordanceAssembler.assembleAffordanceMap();
110
+ affordanceMapSlice = {
111
+ status: "loaded",
112
+ data: affordanceMap,
113
+ };
114
+ }
115
+ catch (err) {
116
+ affordanceMapSlice = {
117
+ status: "degraded",
118
+ data: {},
119
+ reason: `affordance_assembly_failed:${err instanceof Error ? err.message : String(err)}`,
120
+ };
121
+ }
122
+ // ── 7. Self Health (optional) ─────────────────────────────
123
+ let selfHealthSlice;
124
+ if (selfHealthProvider) {
125
+ try {
126
+ const healthResult = await selfHealthProvider.loadSelfHealth();
127
+ selfHealthSlice =
128
+ healthResult.status === "loaded"
129
+ ? { status: "loaded", data: healthResult.data }
130
+ : {
131
+ status: "degraded",
132
+ data: {
133
+ snapshotId: "degraded",
134
+ dimensions: {},
135
+ checkedAt: assembledAt,
136
+ },
137
+ reason: healthResult.reason,
138
+ };
139
+ }
140
+ catch (err) {
141
+ selfHealthSlice = {
142
+ status: "degraded",
143
+ data: {
144
+ snapshotId: "degraded",
145
+ dimensions: {},
146
+ checkedAt: assembledAt,
147
+ },
148
+ reason: `self_health_unavailable:${err instanceof Error ? err.message : String(err)}`,
149
+ };
150
+ }
151
+ }
152
+ return {
153
+ identity: identitySlice,
154
+ goals: goalsSlice,
155
+ recentInteractions: recentInteractionsSlice,
156
+ toolExperience: toolExperienceSlice,
157
+ acceptedDream: acceptedDreamSlice,
158
+ affordanceMap: affordanceMapSlice,
159
+ selfHealth: selfHealthSlice,
160
+ assembledAt,
161
+ };
162
+ },
163
+ };
164
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GoalLifecyclePolicy — T-CP.C.3
3
+ *
4
+ * Core logic: Evaluates active goals, detects replace/expire
5
+ * conditions, and emits typed GoalTransitionRequest.
6
+ *
7
+ * Responsibility separation (DR-012):
8
+ * - control-plane evaluates and emits transition requests.
9
+ * - state-memory executes transitions via GoalLifecycleStore.
10
+ * - This module does NOT write goal state directly.
11
+ *
12
+ * Rules:
13
+ * - Same kind+scope: newest accepted goal stays active; older ones → replaced.
14
+ * - Expired goals (expiresAt < now) → expired transition request.
15
+ *
16
+ * Boundary:
17
+ * - Consumes AgentGoal[] from EmbodiedContext.goals slice.
18
+ * - Returns GoalTransitionRequest[] for state-memory to execute.
19
+ *
20
+ * Test coverage: tests/unit/control-plane/goal-lifecycle-policy.test.ts
21
+ */
22
+ import type { AgentGoal } from "../../../shared/types/goal.js";
23
+ export interface GoalTransitionRequest {
24
+ goalId: string;
25
+ newStatus: "completed" | "expired" | "replaced" | "paused" | "accepted";
26
+ reason: string;
27
+ updatedAt: string;
28
+ }
29
+ export interface GoalLifecyclePolicyResult {
30
+ activeGoals: AgentGoal[];
31
+ transitionRequests: GoalTransitionRequest[];
32
+ evaluatedAt: string;
33
+ }
34
+ export interface GoalLifecyclePolicy {
35
+ evaluate(goals: AgentGoal[]): GoalLifecyclePolicyResult;
36
+ }
37
+ export declare function createGoalLifecyclePolicy(): GoalLifecyclePolicy;
@@ -0,0 +1,61 @@
1
+ export function createGoalLifecyclePolicy() {
2
+ return {
3
+ evaluate(goals) {
4
+ const now = new Date().toISOString();
5
+ const activeGoals = [];
6
+ const transitionRequests = [];
7
+ // Group by kind+scope to detect duplicates among accepted goals
8
+ const groups = new Map();
9
+ for (const goal of goals) {
10
+ if (goal.status !== "accepted")
11
+ continue;
12
+ const key = `${goal.kind}:${goal.scope ?? "global"}`;
13
+ const list = groups.get(key) ?? [];
14
+ list.push(goal);
15
+ groups.set(key, list);
16
+ }
17
+ for (const [key, list] of groups) {
18
+ // Sort by updatedAt desc, keep newest as active
19
+ const sorted = [...list].sort((a, b) => {
20
+ const aTime = new Date(a.updatedAt).getTime();
21
+ const bTime = new Date(b.updatedAt).getTime();
22
+ if (isNaN(aTime) || isNaN(bTime))
23
+ return 0;
24
+ return bTime - aTime;
25
+ });
26
+ const [newest, ...older] = sorted;
27
+ if (newest) {
28
+ activeGoals.push(newest);
29
+ // Older same kind+scope goals → replace
30
+ for (const old of older) {
31
+ transitionRequests.push({
32
+ goalId: old.goalId,
33
+ newStatus: "replaced",
34
+ reason: `same_kind_scope_replace:${key}`,
35
+ updatedAt: now,
36
+ });
37
+ }
38
+ }
39
+ }
40
+ // Detect expired goals among active
41
+ for (const goal of activeGoals) {
42
+ if (goal.expiresAt) {
43
+ const expiresTime = new Date(goal.expiresAt).getTime();
44
+ if (!isNaN(expiresTime) && expiresTime < new Date(now).getTime()) {
45
+ transitionRequests.push({
46
+ goalId: goal.goalId,
47
+ newStatus: "expired",
48
+ reason: "expires_at_reached",
49
+ updatedAt: now,
50
+ });
51
+ }
52
+ }
53
+ }
54
+ return {
55
+ activeGoals,
56
+ transitionRequests,
57
+ evaluatedAt: now,
58
+ };
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * IdleCuriosityPolicy — T-CP.C.3
3
+ *
4
+ * Core logic: When no active goal exists, selects at most one healthy,
5
+ * allowlisted, read-only sensing intent from the affordance map.
6
+ *
7
+ * Rules:
8
+ * - Only read-only capabilities (heuristic: intent ends with .read / .discover / .inspect / .search).
9
+ * - Only safe or exploratory affordance status.
10
+ * - Max one candidate per heartbeat.
11
+ * - 1-hour cooldown per platform.
12
+ * - No eligible connector → reason idle_policy_no_eligible_connector.
13
+ *
14
+ * Boundary:
15
+ * - Does NOT execute connector.
16
+ * - Returns a candidate descriptor, not an execution authorization.
17
+ *
18
+ * Test coverage: tests/unit/control-plane/idle-curiosity-policy.test.ts
19
+ */
20
+ import type { AffordanceMap } from "../../../shared/types/v7-entities.js";
21
+ export interface IdleCuriosityCandidate {
22
+ platformId: string;
23
+ capabilityId: string;
24
+ intent: string;
25
+ reason: string;
26
+ }
27
+ export interface IdleCuriosityPolicyResult {
28
+ candidate?: IdleCuriosityCandidate;
29
+ reason: string;
30
+ }
31
+ export interface IdleCuriosityPolicy {
32
+ select(affordanceMap: AffordanceMap, recentIdleHistory: {
33
+ platformId: string;
34
+ at: string;
35
+ }[]): IdleCuriosityPolicyResult;
36
+ }
37
+ export declare function createIdleCuriosityPolicy(): IdleCuriosityPolicy;
@@ -0,0 +1,60 @@
1
+ const IDLE_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour
2
+ function isReadOnlyIntent(intent) {
3
+ return (intent.endsWith(".read") ||
4
+ intent.endsWith(".discover") ||
5
+ intent.endsWith(".inspect") ||
6
+ intent.endsWith(".search"));
7
+ }
8
+ export function createIdleCuriosityPolicy() {
9
+ return {
10
+ select(affordanceMap, recentIdleHistory) {
11
+ const now = Date.now();
12
+ // Build eligible list
13
+ const eligible = [];
14
+ for (const [platformId, items] of Object.entries(affordanceMap)) {
15
+ for (const item of items) {
16
+ // Read-only only
17
+ if (!isReadOnlyIntent(item.intent))
18
+ continue;
19
+ // Healthy status only
20
+ if (item.status !== "safe" && item.status !== "exploratory")
21
+ continue;
22
+ // Check cooldown
23
+ const lastIdle = recentIdleHistory
24
+ .filter((h) => h.platformId === platformId)
25
+ .sort((a, b) => {
26
+ const aTime = new Date(a.at).getTime();
27
+ const bTime = new Date(b.at).getTime();
28
+ if (isNaN(aTime) || isNaN(bTime))
29
+ return 0;
30
+ return bTime - aTime;
31
+ })[0];
32
+ if (lastIdle) {
33
+ const lastTime = new Date(lastIdle.at).getTime();
34
+ if (!isNaN(lastTime)) {
35
+ const elapsed = now - lastTime;
36
+ if (elapsed < IDLE_COOLDOWN_MS)
37
+ continue;
38
+ }
39
+ }
40
+ eligible.push(item);
41
+ }
42
+ }
43
+ if (eligible.length === 0) {
44
+ return { reason: "idle_policy_no_eligible_connector" };
45
+ }
46
+ // Deterministic selection: first eligible by stable sort
47
+ const chosen = eligible.sort((a, b) => a.platformId.localeCompare(b.platformId) ||
48
+ a.capabilityId.localeCompare(b.capabilityId))[0];
49
+ return {
50
+ candidate: {
51
+ platformId: chosen.platformId,
52
+ capabilityId: chosen.capabilityId,
53
+ intent: chosen.intent,
54
+ reason: "idle_sensing_selected",
55
+ },
56
+ reason: "idle_sensing_selected",
57
+ };
58
+ },
59
+ };
60
+ }
@@ -6,3 +6,7 @@ export { buildPlannerRhythmWindow, type PlannerRhythmWindowSlice } from "../rhyt
6
6
  export { runHeartbeatCycle, type RunHeartbeatCycleInput } from "./run-heartbeat-cycle.js";
7
7
  export { routeScopedInput, type ScopeRouterDeps, } from "./scope-router.js";
8
8
  export { requestGuidanceForIntent, dispatchAllowedEffect, executeHeartbeatCycle, type GuidanceBridgeDeps, type EffectDispatchDeps, type HeartbeatExecutorDeps, type GuidanceBridgeResult, type HeartbeatExecutionResult, } from "./heartbeat-executor.js";
9
+ export { runHeartbeatV7, type HeartbeatDecision, type HeartbeatV7Deps, type RunHeartbeatV7Input, } from "./run-heartbeat-cycle-v7.js";
10
+ export { createDecisionTraceEmitter, createNoOpTraceEmitter, type DecisionTracePayload, type DecisionTraceEmitter, } from "./decision-trace-emitter.js";
11
+ export { createGoalLifecyclePolicy, type GoalLifecyclePolicy, type GoalTransitionRequest, type GoalLifecyclePolicyResult, } from "./goal-lifecycle-policy.js";
12
+ export { createIdleCuriosityPolicy, type IdleCuriosityPolicy, type IdleCuriosityCandidate, type IdleCuriosityPolicyResult, } from "./idle-curiosity-policy.js";
@@ -5,3 +5,8 @@ export { buildPlannerRhythmWindow } from "../rhythm/planner-rhythm-window.js";
5
5
  export { runHeartbeatCycle } from "./run-heartbeat-cycle.js";
6
6
  export { routeScopedInput, } from "./scope-router.js";
7
7
  export { requestGuidanceForIntent, dispatchAllowedEffect, executeHeartbeatCycle, } from "./heartbeat-executor.js";
8
+ /* Wave 57 — v7 heartbeat main loop + goal/idle policy */
9
+ export { runHeartbeatV7, } from "./run-heartbeat-cycle-v7.js";
10
+ export { createDecisionTraceEmitter, createNoOpTraceEmitter, } from "./decision-trace-emitter.js";
11
+ export { createGoalLifecyclePolicy, } from "./goal-lifecycle-policy.js";
12
+ export { createIdleCuriosityPolicy, } from "./idle-curiosity-policy.js";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * runHeartbeatV7 — T-CP.C.2
3
+ *
4
+ * Core logic: v7 heartbeat main loop entry.
5
+ * - Scope routing (rhythm / user_task / user_reply)
6
+ * - EmbodiedContext assembly via EmbodiedContextAssembler
7
+ * - Candidate planning via injected CandidateIntentPlanner
8
+ * - Hard guard evaluation with affordance + breaker awareness
9
+ * - Downstream request orchestration
10
+ * - Decision trace emission
11
+ *
12
+ * Performance: assembly + planning + guards P95 < 2s.
13
+ * Exceeding samples are marked degraded but do not crash.
14
+ *
15
+ * Boundary:
16
+ * - Does NOT write state, execute connectors, or generate guidance copy.
17
+ * - Returns HeartbeatDecision; downstream execution is delegated.
18
+ *
19
+ * Dependencies:
20
+ * - EmbodiedContextAssembler from ./embodied-context-assembler.js
21
+ * - evaluateHardGuards from ../orchestrator/hard-guard-evaluator.js
22
+ * - createDownstreamIntentOrchestrator from ../orchestrator/downstream-intent-orchestrator.js
23
+ * - createDecisionTraceEmitter from ./decision-trace-emitter.js
24
+ * - routeScopedInput from ./scope-router.js
25
+ *
26
+ * Test coverage:
27
+ * - tests/unit/control-plane/run-heartbeat-cycle-v7.test.ts
28
+ * - tests/integration/control-plane/heartbeat-loop.test.ts
29
+ */
30
+ import type { HeartbeatSignal, RuntimeScope } from "./signal.js";
31
+ import type { EmbodiedContextAssembler } from "./embodied-context-assembler.js";
32
+ import type { CandidateIntent, GuardEvaluation } from "../types.js";
33
+ import type { HardGuardEvaluatorDeps } from "../orchestrator/hard-guard-evaluator.js";
34
+ import type { DownstreamIntentOrchestrator } from "../orchestrator/downstream-intent-orchestrator.js";
35
+ import type { DecisionTraceEmitter } from "./decision-trace-emitter.js";
36
+ import type { EmbodiedContext } from "../../../shared/types/v7-entities.js";
37
+ export type HeartbeatDecisionStatus = "heartbeat_ok" | "intent_selected" | "deferred" | "denied" | "delivery_unavailable" | "runtime_carrier_only";
38
+ export interface HeartbeatDecision {
39
+ decisionId: string;
40
+ scope: RuntimeScope;
41
+ status: HeartbeatDecisionStatus;
42
+ selectedIntentId?: string;
43
+ downstreamRequestId?: string;
44
+ reasons: string[];
45
+ contextId?: string;
46
+ }
47
+ export interface CandidateIntentPlanner {
48
+ planCandidates(context: EmbodiedContext): CandidateIntent[];
49
+ }
50
+ export interface HeartbeatV7Deps {
51
+ assembler: EmbodiedContextAssembler;
52
+ planner: CandidateIntentPlanner;
53
+ evaluateHardGuards: (intent: CandidateIntent, deps: HardGuardEvaluatorDeps) => GuardEvaluation;
54
+ buildGuardDeps: (context: EmbodiedContext) => HardGuardEvaluatorDeps;
55
+ downstreamOrchestrator: DownstreamIntentOrchestrator;
56
+ traceEmitter: DecisionTraceEmitter;
57
+ }
58
+ export interface RunHeartbeatV7Input {
59
+ signal: HeartbeatSignal;
60
+ runtimeAvailable: boolean;
61
+ deps: HeartbeatV7Deps;
62
+ }
63
+ export declare function runHeartbeatV7(input: RunHeartbeatV7Input): Promise<HeartbeatDecision>;
@@ -0,0 +1,118 @@
1
+ import { routeScopedInput } from "./scope-router.js";
2
+ const P95_MS = 2000;
3
+ export async function runHeartbeatV7(input) {
4
+ const { signal, runtimeAvailable, deps } = input;
5
+ // ── Scope routing ─────────────────────────────────────────────
6
+ const payload = typeof signal.payload === "object" &&
7
+ signal.payload !== null &&
8
+ !Array.isArray(signal.payload)
9
+ ? signal.payload
10
+ : {};
11
+ const scoped = {
12
+ trigger: signal.trigger,
13
+ scopeHint: signal.scopeHint,
14
+ payload,
15
+ };
16
+ const route = routeScopedInput(scoped);
17
+ // Runtime availability gate
18
+ if (!runtimeAvailable) {
19
+ return {
20
+ decisionId: `decision:carrier:${Date.now()}`,
21
+ scope: route.scope,
22
+ status: "runtime_carrier_only",
23
+ reasons: ["runtime_unavailable_no_lived_experience_loop"],
24
+ };
25
+ }
26
+ // User task bypass
27
+ if (route.scope === "user_task") {
28
+ return {
29
+ decisionId: `decision:user_task:${Date.now()}`,
30
+ scope: "user_task",
31
+ status: "heartbeat_ok",
32
+ reasons: ["rhythm_gate_bypass_user_task"],
33
+ };
34
+ }
35
+ // User reply light path
36
+ if (route.scope === "user_reply") {
37
+ return {
38
+ decisionId: `decision:user_reply:${Date.now()}`,
39
+ scope: "user_reply",
40
+ status: "heartbeat_ok",
41
+ reasons: ["user_reply_light_continuity_skeleton"],
42
+ };
43
+ }
44
+ // ── Rhythm path: assemble embodied context ──────────────────
45
+ const assemblyStart = Date.now();
46
+ const context = await deps.assembler.assembleEmbodiedContext();
47
+ const assemblyMs = Date.now() - assemblyStart;
48
+ const contextId = `ctx:${Date.now()}`;
49
+ // Build guard deps from assembled context
50
+ const guardDeps = deps.buildGuardDeps(context);
51
+ // Plan candidates
52
+ const candidates = deps.planner.planCandidates(context);
53
+ // Evaluate guards and select first allowed
54
+ for (const intent of candidates) {
55
+ const evaluation = deps.evaluateHardGuards(intent, guardDeps);
56
+ if (evaluation.verdict === "allow") {
57
+ const downstream = deps.downstreamOrchestrator.orchestrate(intent);
58
+ const decisionId = `decision:${intent.id}:${Date.now()}`;
59
+ const result = {
60
+ decisionId,
61
+ scope: "rhythm",
62
+ status: "intent_selected",
63
+ selectedIntentId: intent.id,
64
+ downstreamRequestId: downstream.kind === "none"
65
+ ? undefined
66
+ : `${downstream.kind}:${decisionId}`,
67
+ reasons: evaluation.reasons.length > 0
68
+ ? evaluation.reasons
69
+ : ["guard_allow"],
70
+ contextId,
71
+ };
72
+ // P95 degradation note (does not change status per T-CP.C.2 acceptance)
73
+ if (assemblyMs > P95_MS) {
74
+ result.reasons.push(`heartbeat_degraded:assembly_p95_exceeded:${assemblyMs}ms`);
75
+ }
76
+ await safeEmitTrace(deps.traceEmitter, result, contextId);
77
+ return result;
78
+ }
79
+ if (evaluation.verdict === "defer") {
80
+ continue;
81
+ }
82
+ // deny / escalate → continue to next candidate
83
+ }
84
+ // ── No allowed candidates ────────────────────────────────────
85
+ const decisionId = `decision:no_allow:${Date.now()}`;
86
+ const result = {
87
+ decisionId,
88
+ scope: "rhythm",
89
+ status: candidates.length === 0 ? "heartbeat_ok" : "deferred",
90
+ reasons: candidates.length === 0
91
+ ? ["silent_no_candidates"]
92
+ : ["no_allow_verdict"],
93
+ contextId,
94
+ };
95
+ if (assemblyMs > P95_MS) {
96
+ result.reasons.push(`heartbeat_degraded:assembly_p95_exceeded:${assemblyMs}ms`);
97
+ }
98
+ await safeEmitTrace(deps.traceEmitter, result, contextId);
99
+ return result;
100
+ }
101
+ async function safeEmitTrace(emitter, decision, contextId) {
102
+ const trace = {
103
+ traceId: `trace:${decision.decisionId}`,
104
+ decisionId: decision.decisionId,
105
+ contextId,
106
+ scope: decision.scope,
107
+ status: decision.status,
108
+ reasons: decision.reasons,
109
+ selectedIntentId: decision.selectedIntentId,
110
+ emittedAt: new Date().toISOString(),
111
+ };
112
+ try {
113
+ await emitter.emit(trace);
114
+ }
115
+ catch {
116
+ // Trace emission must not block the heartbeat cycle
117
+ }
118
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * DownstreamIntentOrchestrator — T-CP.C.2
3
+ *
4
+ * Core logic: Maps an allowed CandidateIntent to a typed downstream request.
5
+ * Does NOT own downstream implementation — only constructs the request envelope.
6
+ *
7
+ * Boundary:
8
+ * - Returns typed request objects; caller delegates to connector-system,
9
+ * dream-quiet-system, or guidance-voice-system.
10
+ * - maintenance/no_effect intents produce "none" requests (no downstream).
11
+ *
12
+ * Test coverage: tests/unit/control-plane/downstream-intent-orchestrator.test.ts
13
+ */
14
+ import type { CandidateIntent } from "../types.js";
15
+ export interface ConnectorIntentRequest {
16
+ kind: "connector_intent";
17
+ platformId: string;
18
+ capabilityId?: string;
19
+ payload: Record<string, unknown>;
20
+ }
21
+ export interface QuietRunRequest {
22
+ kind: "quiet_run";
23
+ reason: string;
24
+ }
25
+ export interface DreamScheduleRequest {
26
+ kind: "dream_schedule";
27
+ reason: string;
28
+ }
29
+ export interface GuidanceDraftRequest {
30
+ kind: "guidance_draft";
31
+ targetChannel?: string;
32
+ evidenceRefs: string[];
33
+ }
34
+ export type DownstreamRequest = ConnectorIntentRequest | QuietRunRequest | DreamScheduleRequest | GuidanceDraftRequest | {
35
+ kind: "none";
36
+ reason: string;
37
+ };
38
+ export interface DownstreamIntentOrchestrator {
39
+ orchestrate(intent: CandidateIntent): DownstreamRequest;
40
+ }
41
+ export declare function createDownstreamIntentOrchestrator(): DownstreamIntentOrchestrator;
@@ -0,0 +1,43 @@
1
+ export function createDownstreamIntentOrchestrator() {
2
+ return {
3
+ orchestrate(intent) {
4
+ switch (intent.effectClass) {
5
+ case "connector_action":
6
+ case "external_platform_action":
7
+ return {
8
+ kind: "connector_intent",
9
+ platformId: intent.platformId ?? "unknown",
10
+ capabilityId: intent.capabilityIntent,
11
+ payload: {
12
+ sourceRefs: intent.sourceRefs.map((r) => r.id),
13
+ summary: intent.summary,
14
+ },
15
+ };
16
+ case "user_outreach":
17
+ return {
18
+ kind: "guidance_draft",
19
+ targetChannel: intent.platformId,
20
+ evidenceRefs: intent.sourceRefs.map((r) => r.id),
21
+ };
22
+ case "narrative_reflection":
23
+ return { kind: "quiet_run", reason: "narrative_reflection" };
24
+ case "maintenance":
25
+ case "no_effect":
26
+ return {
27
+ kind: "none",
28
+ reason: "no_downstream_for_maintenance",
29
+ };
30
+ case "memory_curation":
31
+ return {
32
+ kind: "dream_schedule",
33
+ reason: "memory_curation",
34
+ };
35
+ default:
36
+ return {
37
+ kind: "none",
38
+ reason: `unhandled_effect_class:${intent.effectClass}`,
39
+ };
40
+ }
41
+ },
42
+ };
43
+ }
@@ -6,6 +6,7 @@ export interface AllowedIntent {
6
6
  kind: "work" | "exploration" | "social" | "quiet" | "reflection" | "outreach" | "maintenance";
7
7
  summary: string;
8
8
  effectClass: EffectClass;
9
+ capabilityIntent?: string;
9
10
  platformId?: string;
10
11
  payload?: Record<string, unknown>;
11
12
  }
@@ -74,7 +75,7 @@ export type DispatchResult = {
74
75
  status: "maintenance_done";
75
76
  commitId: string;
76
77
  };
77
- export declare function toCapabilityIntent(intent: Pick<AllowedIntent, "kind">): CapabilityIntent;
78
+ export declare function toCapabilityIntent(intent: Pick<AllowedIntent, "kind" | "capabilityIntent">): CapabilityIntent;
78
79
  export declare class EffectDispatcher {
79
80
  private readonly leaseManager;
80
81
  private readonly commitPort;