@haaaiawd/second-nature 0.1.43 → 0.1.51

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.
@@ -1,37 +1,24 @@
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;
1
+ export interface GoalTransitionRequest {
2
+ goalId: string;
3
+ newStatus: "completed" | "expired" | "replaced" | "paused" | "accepted";
4
+ reason: string;
5
+ updatedAt: string;
6
+ }
7
+ /** Minimal goal shape required by the lifecycle policy evaluator. */
8
+ export interface EvaluableGoal {
9
+ goalId: string;
10
+ kind?: string;
11
+ scope?: string;
12
+ status: string;
13
+ updatedAt?: string;
14
+ expiresAt?: string;
15
+ }
16
+ export interface GoalLifecyclePolicyResult {
17
+ activeGoals: EvaluableGoal[];
18
+ transitionRequests: GoalTransitionRequest[];
19
+ evaluatedAt: string;
20
+ }
21
+ export interface GoalLifecyclePolicy {
22
+ evaluate(goals: EvaluableGoal[]): GoalLifecyclePolicyResult;
23
+ }
24
+ export declare function createGoalLifecyclePolicy(): GoalLifecyclePolicy;
@@ -1,61 +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
- }
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 ?? 0).getTime();
21
+ const bTime = new Date(b.updatedAt ?? 0).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
+ }
@@ -1,88 +1,97 @@
1
- /**
2
- * Heartbeat Decision Loop
3
- *
4
- * Main entry point for the heartbeat runtime. Accepts a HeartbeatSignal,
5
- * builds runtime snapshot, plans candidate intents, evaluates hard guards,
6
- * and returns a HeartbeatCycleResult.
7
- *
8
- * Per design doc §4.3: heartbeat round follows the sequence:
9
- * signal → snapshot → plan → guard → result (HEARTBEAT_OK or intent_selected)
10
- *
11
- * Per ADR-005: heartbeat is the free-rhythm main entry; this loop
12
- * implements the default conservative path where HEARTBEAT_OK is
13
- * the first-class result when no action is warranted.
14
- */
15
- import type { HeartbeatSignal, HeartbeatCycleResult, HeartbeatCycleStatus, RuntimeScope, RuntimeTrigger } from "./signal.js";
16
- import type { CandidateIntent, ContinuitySnapshot, IntentKind } from "../types.js";
17
- import { type SnapshotInputs } from "./snapshot-builder.js";
18
- import { type HeartbeatRuntimeSnapshot } from "./runtime-snapshot.js";
19
- import type { GuidanceDraftPort } from "../../../guidance/outreach-draft-schema.js";
20
- import type { StateDatabase } from "../../../storage/db/index.js";
21
- import { type OpenClawDeliveryPort } from "../outreach/dispatch-user-outreach.js";
22
- import type { ConnectorExecutor } from "../../../connectors/base/contract.js";
23
- import type { CapabilityContractRegistry } from "../../../connectors/base/manifest.js";
24
- import type { NarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
25
- import type { NarrativeTracePayload } from "../../../observability/services/lived-experience-audit.js";
26
- import type { ExperienceWriter } from "../body/tool-experience/experience-writer.js";
27
- import type { QuietDreamSchedulePort } from "../quiet/run-source-backed-quiet.js";
28
- export interface HeartbeatDecisionTracePayload {
29
- scope: RuntimeScope;
30
- status: HeartbeatCycleStatus;
31
- reasons: string[];
32
- selectedIntentId?: string;
33
- rhythmWindowId: string;
34
- allowedIntentKinds: IntentKind[];
35
- candidateCount: number;
36
- lifeEvidenceEmpty: boolean;
37
- trigger: RuntimeTrigger;
38
- }
39
- /** Optional outreach delivery chain: when set, first allowed `user_outreach` runs dispatch (CR-M1). */
40
- export interface HeartbeatOutreachDispatchDeps {
41
- state: StateDatabase;
42
- guidance: GuidanceDraftPort;
43
- delivery: OpenClawDeliveryPort;
44
- }
45
- /** Optional Quiet orchestration: when set, quiet/reflection allows run source-backed Quiet writer (T2.3.3). */
46
- export interface HeartbeatQuietWorkflowDeps {
47
- workspaceRoot: string;
48
- /** v7 T-V7C.C.3: when present, a successful Quiet write auto-triggers Dream scheduling. */
49
- dreamSchedulePort?: QuietDreamSchedulePort;
50
- }
51
- /**
52
- * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
53
- * Exported for unit tests (CR-M1 wiring).
54
- */
55
- export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter">): Promise<HeartbeatCycleResult>;
56
- export interface HeartbeatDeps {
57
- /** Load snapshot inputs from state-system */
58
- loadSnapshotInputs: () => Promise<SnapshotInputs>;
59
- /** Optional observability hook (T2.2.1): one record per completed cycle. */
60
- recordDecisionTrace?: (payload: HeartbeatDecisionTracePayload) => Promise<void>;
61
- outreachDispatch?: HeartbeatOutreachDispatchDeps;
62
- quietWorkflow?: HeartbeatQuietWorkflowDeps;
63
- /**
64
- * When present, guard-allowed connector_action intents are dispatched
65
- * through the connector-system instead of returning connector_dispatch_unwired.
66
- */
67
- connectorExecutor?: ConnectorExecutor;
68
- /** T2.1.5: when present, heartbeat writes a source-backed NarrativeState revision after each cycle. */
69
- narrativeStateStore?: NarrativeStateStore;
70
- /** T5.1.2: when present, heartbeat records a NarrativeTrace after successful narrative state update. */
71
- recordNarrativeTrace?: (payload: NarrativeTracePayload) => Promise<void>;
72
- /** T3.3.1: when present, successful connector effects write LifeEvidence artifacts. */
73
- state?: StateDatabase;
74
- /** T3.3.1: workspace root for evidence artifact paths. */
75
- workspaceRoot?: string;
76
- /** T2.4.1: when present, planner resolves platform-specific intents. */
77
- connectorRegistry?: CapabilityContractRegistry;
78
- /** v7 T-V7C.C.2: when present, connector attempts write ToolExperience with triggerSource="heartbeat". */
79
- experienceWriter?: ExperienceWriter;
80
- }
81
- /**
82
- * Ingest a heartbeat rhythm signal and drive one full decision round.
83
- */
84
- export declare function ingestRhythmSignal(signal: HeartbeatSignal, deps: HeartbeatDeps): Promise<HeartbeatCycleResult>;
85
- /**
86
- * Build a snapshot directly from inputs (for testing or when state-system is unavailable).
87
- */
88
- export declare function buildSnapshotFromInputs(inputs: SnapshotInputs): ContinuitySnapshot;
1
+ /**
2
+ * Heartbeat Decision Loop
3
+ *
4
+ * Main entry point for the heartbeat runtime. Accepts a HeartbeatSignal,
5
+ * builds runtime snapshot, plans candidate intents, evaluates hard guards,
6
+ * and returns a HeartbeatCycleResult.
7
+ *
8
+ * Per design doc §4.3: heartbeat round follows the sequence:
9
+ * signal → snapshot → plan → guard → result (HEARTBEAT_OK or intent_selected)
10
+ *
11
+ * Per ADR-005: heartbeat is the free-rhythm main entry; this loop
12
+ * implements the default conservative path where HEARTBEAT_OK is
13
+ * the first-class result when no action is warranted.
14
+ */
15
+ import type { HeartbeatSignal, HeartbeatCycleResult, HeartbeatCycleStatus, RuntimeScope, RuntimeTrigger } from "./signal.js";
16
+ import type { CandidateIntent, ContinuitySnapshot, IntentKind } from "../types.js";
17
+ import { type SnapshotInputs } from "./snapshot-builder.js";
18
+ import { type HeartbeatRuntimeSnapshot } from "./runtime-snapshot.js";
19
+ import type { GuidanceDraftPort } from "../../../guidance/outreach-draft-schema.js";
20
+ import type { StateDatabase } from "../../../storage/db/index.js";
21
+ import { type OpenClawDeliveryPort } from "../outreach/dispatch-user-outreach.js";
22
+ import type { ConnectorExecutor } from "../../../connectors/base/contract.js";
23
+ import type { CapabilityContractRegistry } from "../../../connectors/base/manifest.js";
24
+ import type { NarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
25
+ import type { NarrativeTracePayload } from "../../../observability/services/lived-experience-audit.js";
26
+ import type { ExperienceWriter } from "../body/tool-experience/experience-writer.js";
27
+ import type { QuietDreamSchedulePort } from "../quiet/run-source-backed-quiet.js";
28
+ import type { GoalLifecyclePolicy } from "./goal-lifecycle-policy.js";
29
+ import type { IdleCuriosityPolicy } from "./idle-curiosity-policy.js";
30
+ import type { CircuitBreakerManager } from "../body/circuit-breaker/circuit-breaker-manager.js";
31
+ export interface HeartbeatDecisionTracePayload {
32
+ scope: RuntimeScope;
33
+ status: HeartbeatCycleStatus;
34
+ reasons: string[];
35
+ selectedIntentId?: string;
36
+ rhythmWindowId: string;
37
+ allowedIntentKinds: IntentKind[];
38
+ candidateCount: number;
39
+ lifeEvidenceEmpty: boolean;
40
+ trigger: RuntimeTrigger;
41
+ }
42
+ /** Optional outreach delivery chain: when set, first allowed `user_outreach` runs dispatch (CR-M1). */
43
+ export interface HeartbeatOutreachDispatchDeps {
44
+ state: StateDatabase;
45
+ guidance: GuidanceDraftPort;
46
+ delivery: OpenClawDeliveryPort;
47
+ }
48
+ /** Optional Quiet orchestration: when set, quiet/reflection allows run source-backed Quiet writer (T2.3.3). */
49
+ export interface HeartbeatQuietWorkflowDeps {
50
+ workspaceRoot: string;
51
+ /** v7 T-V7C.C.3: when present, a successful Quiet write auto-triggers Dream scheduling. */
52
+ dreamSchedulePort?: QuietDreamSchedulePort;
53
+ }
54
+ /**
55
+ * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
56
+ * Exported for unit tests (CR-M1 wiring).
57
+ */
58
+ export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter" | "circuitBreakerManager">): Promise<HeartbeatCycleResult>;
59
+ export interface HeartbeatDeps {
60
+ /** Load snapshot inputs from state-system */
61
+ loadSnapshotInputs: () => Promise<SnapshotInputs>;
62
+ /** Optional observability hook (T2.2.1): one record per completed cycle. */
63
+ recordDecisionTrace?: (payload: HeartbeatDecisionTracePayload) => Promise<void>;
64
+ outreachDispatch?: HeartbeatOutreachDispatchDeps;
65
+ quietWorkflow?: HeartbeatQuietWorkflowDeps;
66
+ /**
67
+ * When present, guard-allowed connector_action intents are dispatched
68
+ * through the connector-system instead of returning connector_dispatch_unwired.
69
+ */
70
+ connectorExecutor?: ConnectorExecutor;
71
+ /** T2.1.5: when present, heartbeat writes a source-backed NarrativeState revision after each cycle. */
72
+ narrativeStateStore?: NarrativeStateStore;
73
+ /** T5.1.2: when present, heartbeat records a NarrativeTrace after successful narrative state update. */
74
+ recordNarrativeTrace?: (payload: NarrativeTracePayload) => Promise<void>;
75
+ /** T3.3.1: when present, successful connector effects write LifeEvidence artifacts. */
76
+ state?: StateDatabase;
77
+ /** T3.3.1: workspace root for evidence artifact paths. */
78
+ workspaceRoot?: string;
79
+ /** T2.4.1: when present, planner resolves platform-specific intents. */
80
+ connectorRegistry?: CapabilityContractRegistry;
81
+ /** v7 T-V7C.C.2: when present, connector attempts write ToolExperience with triggerSource="heartbeat". */
82
+ experienceWriter?: ExperienceWriter;
83
+ /** v7 T-CP.C.3: when present, evaluates goal lifecycle transitions before candidate planning. */
84
+ goalLifecyclePolicy?: GoalLifecyclePolicy;
85
+ /** v7 T-CP.C.3: when present, selects read-only sensing intent when no active goals exist. */
86
+ idleCuriosityPolicy?: IdleCuriosityPolicy;
87
+ /** v7 T-BTS.C.5: when present, updates breaker state after connector execution. */
88
+ circuitBreakerManager?: CircuitBreakerManager;
89
+ }
90
+ /**
91
+ * Ingest a heartbeat rhythm signal and drive one full decision round.
92
+ */
93
+ export declare function ingestRhythmSignal(signal: HeartbeatSignal, deps: HeartbeatDeps): Promise<HeartbeatCycleResult>;
94
+ /**
95
+ * Build a snapshot directly from inputs (for testing or when state-system is unavailable).
96
+ */
97
+ export declare function buildSnapshotFromInputs(inputs: SnapshotInputs): ContinuitySnapshot;