@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.
- package/index.js +73 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/index.d.ts +2 -0
- package/runtime/cli/commands/index.js +61 -6
- package/runtime/cli/explain/explain-surface-subject.d.ts +8 -0
- package/runtime/cli/explain/explain-surface-subject.js +9 -0
- package/runtime/cli/explain/format-explanation.d.ts +2 -0
- package/runtime/cli/explain/format-explanation.js +2 -0
- package/runtime/cli/explain/resolve-subject.js +15 -0
- package/runtime/cli/host-capability/classify-delivery.d.ts +14 -0
- package/runtime/cli/host-capability/classify-delivery.js +20 -0
- package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -0
- package/runtime/cli/host-capability/probe-host-capability.js +58 -0
- package/runtime/cli/host-capability/record-host-capability.d.ts +6 -0
- package/runtime/cli/host-capability/record-host-capability.js +14 -0
- package/runtime/cli/host-capability/types.d.ts +71 -0
- package/runtime/cli/host-capability/types.js +6 -0
- package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -0
- package/runtime/cli/host-smoke/run-host-smoke.js +40 -0
- package/runtime/cli/host-smoke/types.d.ts +35 -0
- package/runtime/cli/host-smoke/types.js +6 -0
- package/runtime/cli/index.js +18 -0
- package/runtime/cli/ops/heartbeat-surface.d.ts +35 -0
- package/runtime/cli/ops/heartbeat-surface.js +71 -0
- package/runtime/cli/ops/ops-router.d.ts +16 -0
- package/runtime/cli/ops/ops-router.js +83 -0
- package/runtime/cli/ops/show-operator-fallback.d.ts +13 -0
- package/runtime/cli/ops/show-operator-fallback.js +22 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +10 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +26 -0
- package/runtime/cli/read-models/index.d.ts +11 -2
- package/runtime/cli/read-models/index.js +50 -0
- package/runtime/cli/read-models/operator-explain-map.d.ts +6 -0
- package/runtime/cli/read-models/operator-explain-map.js +10 -0
- package/runtime/cli/read-models/types.d.ts +5 -1
- package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -0
- package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -0
- package/runtime/connectors/base/contract.d.ts +6 -0
- package/runtime/connectors/base/execution-policy.d.ts +47 -0
- package/runtime/connectors/base/execution-policy.js +82 -0
- package/runtime/connectors/base/index.d.ts +2 -0
- package/runtime/connectors/base/index.js +2 -0
- package/runtime/connectors/base/manifest.d.ts +55 -2
- package/runtime/connectors/base/manifest.js +50 -0
- package/runtime/connectors/base/map-life-evidence.d.ts +16 -0
- package/runtime/connectors/base/map-life-evidence.js +79 -0
- package/runtime/connectors/base/policy-layer.d.ts +2 -0
- package/runtime/connectors/base/policy-layer.js +16 -0
- package/runtime/connectors/base/route-planner.js +1 -0
- package/runtime/connectors/index.d.ts +1 -0
- package/runtime/connectors/index.js +1 -0
- package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -0
- package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +2 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +37 -16
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +95 -29
- package/runtime/core/second-nature/heartbeat/index.d.ts +4 -1
- package/runtime/core/second-nature/heartbeat/index.js +4 -1
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -0
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -0
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -0
- package/runtime/core/second-nature/heartbeat/signal.d.ts +9 -2
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +19 -1
- package/runtime/core/second-nature/index.d.ts +8 -0
- package/runtime/core/second-nature/index.js +8 -0
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +1 -1
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +9 -4
- package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +6 -0
- package/runtime/core/second-nature/orchestrator/guard-layer.js +76 -20
- package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +10 -0
- package/runtime/core/second-nature/orchestrator/intent-planner.js +135 -28
- package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +1 -1
- package/runtime/core/second-nature/orchestrator/lease-manager.js +1 -1
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -0
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -0
- package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -0
- package/runtime/core/second-nature/outreach/delivery-target.js +70 -0
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -0
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -0
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -0
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -0
- package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -0
- package/runtime/core/second-nature/outreach/judge-outreach.js +121 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -0
- package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -0
- package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -0
- package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -0
- package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -0
- package/runtime/core/second-nature/types.d.ts +16 -2
- package/runtime/guidance/draft-outreach-message.d.ts +7 -0
- package/runtime/guidance/draft-outreach-message.js +42 -0
- package/runtime/guidance/evidence-guidance.d.ts +40 -0
- package/runtime/guidance/evidence-guidance.js +52 -0
- package/runtime/guidance/index.d.ts +3 -0
- package/runtime/guidance/index.js +3 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +228 -0
- package/runtime/guidance/outreach-draft-schema.js +80 -0
- package/runtime/observability/audit/append-only-audit-store.d.ts +14 -0
- package/runtime/observability/audit/append-only-audit-store.js +21 -0
- package/runtime/observability/audit/audit-envelope.d.ts +51 -0
- package/runtime/observability/audit/audit-envelope.js +130 -0
- package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -0
- package/runtime/observability/audit/verify-audit-hash-chain.js +83 -0
- package/runtime/observability/db/index.js +11 -0
- package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -0
- package/runtime/observability/db/schema/host-capability-reports.js +12 -0
- package/runtime/observability/db/schema/index.d.ts +1 -0
- package/runtime/observability/db/schema/index.js +1 -0
- package/runtime/observability/index.d.ts +7 -0
- package/runtime/observability/index.js +7 -0
- package/runtime/observability/query/explain-query.d.ts +48 -0
- package/runtime/observability/query/explain-query.js +114 -0
- package/runtime/observability/query/export-audit-bundle.d.ts +22 -0
- package/runtime/observability/query/export-audit-bundle.js +27 -0
- package/runtime/observability/services/decision-ledger.d.ts +1 -1
- package/runtime/observability/services/decision-ledger.js +4 -0
- package/runtime/observability/services/governance-audit.d.ts +14 -0
- package/runtime/observability/services/governance-audit.js +25 -1
- package/runtime/observability/services/governance-plane-recorder.d.ts +47 -0
- package/runtime/observability/services/governance-plane-recorder.js +55 -0
- package/runtime/observability/services/lived-experience-audit.d.ts +97 -0
- package/runtime/observability/services/lived-experience-audit.js +161 -0
- package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -0
- package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -0
- package/runtime/storage/bootstrap/repair-gate.d.ts +17 -0
- package/runtime/storage/bootstrap/repair-gate.js +71 -0
- package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -0
- package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -0
- package/runtime/storage/db/index.js +49 -0
- package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -0
- package/runtime/storage/db/schema/delivery-attempts.js +13 -0
- package/runtime/storage/db/schema/index.d.ts +3 -0
- package/runtime/storage/db/schema/index.js +3 -0
- package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -0
- package/runtime/storage/db/schema/life-evidence-index.js +11 -0
- package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -0
- package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -0
- package/runtime/storage/db/schema/policies.d.ts +17 -0
- package/runtime/storage/db/schema/policies.js +1 -0
- package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -0
- package/runtime/storage/delivery/query-delivery-attempts.js +32 -0
- package/runtime/storage/delivery/types.d.ts +27 -0
- package/runtime/storage/delivery/types.js +1 -0
- package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -0
- package/runtime/storage/delivery/write-delivery-attempt.js +36 -0
- package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -0
- package/runtime/storage/fallback/load-operator-fallback.js +47 -0
- package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -0
- package/runtime/storage/fallback/operator-fallback-types.js +1 -0
- package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -0
- package/runtime/storage/fallback/operator-fallback-view.js +1 -0
- package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -0
- package/runtime/storage/fallback/write-operator-fallback.js +21 -0
- package/runtime/storage/index.d.ts +21 -0
- package/runtime/storage/index.js +14 -0
- package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -0
- package/runtime/storage/life-evidence/append-life-evidence.js +64 -0
- package/runtime/storage/life-evidence/types.d.ts +45 -0
- package/runtime/storage/life-evidence/types.js +6 -0
- package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -0
- package/runtime/storage/quiet/persist-quiet-artifact.js +22 -0
- package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -0
- package/runtime/storage/quiet/quiet-artifact-types.js +1 -0
- package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -0
- package/runtime/storage/quiet/quiet-artifact-writer.js +56 -0
- package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -0
- package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -0
- package/runtime/storage/services/credential-vault.d.ts +5 -0
- package/runtime/storage/services/credential-vault.js +46 -9
- package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -0
- package/runtime/storage/snapshots/continuity-snapshot.js +41 -0
- package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -0
- package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -0
- package/runtime/storage/snapshots/types.d.ts +58 -0
- package/runtime/storage/snapshots/types.js +1 -0
- package/runtime/storage/state-api.js +11 -4
- package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -0
- package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -0
- package/runtime/storage/user-interest/types.d.ts +25 -0
- package/runtime/storage/user-interest/types.js +1 -0
|
@@ -1,69 +1,135 @@
|
|
|
1
1
|
import { buildContinuitySnapshot } from "./snapshot-builder.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
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 =
|
|
75
|
+
const evaluation = evaluateHardGuards(intent, runtime);
|
|
33
76
|
if (evaluation.verdict === "allow") {
|
|
34
77
|
anyAllow = true;
|
|
35
|
-
|
|
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
|
-
|
|
47
|
-
return {
|
|
100
|
+
const result = {
|
|
48
101
|
scope: "rhythm",
|
|
49
102
|
status: "heartbeat_ok",
|
|
50
|
-
reasons: ["
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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,
|
|
16
|
-
if (
|
|
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
|
|
22
|
+
return false;
|
|
22
23
|
}
|
|
23
|
-
|
|
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 (
|
|
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" ||
|
|
43
|
-
|
|
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("
|
|
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;
|