@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane outreach judgment (T2.3.1): value / interest / actionability / delivery / dedupe.
|
|
3
|
+
*
|
|
4
|
+
* Does not call guidance or perform delivery; callers supply guard-derived cooldown/duplicate flags.
|
|
5
|
+
* Test coverage: tests/unit/core/outreach-judgment.test.ts
|
|
6
|
+
*/
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
import { isDeliveryUnavailableReason, resolveDeliveryTarget, } from "./delivery-target.js";
|
|
9
|
+
const OUTREACH_POLICY = {
|
|
10
|
+
minValueScore: 0.35,
|
|
11
|
+
minUserRelevance: 0.4,
|
|
12
|
+
minActionabilityWhenInterestLow: 0.7,
|
|
13
|
+
};
|
|
14
|
+
function tokenize(text) {
|
|
15
|
+
return text
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.split(/[^a-z0-9\u4e00-\u9fff]+/g)
|
|
18
|
+
.filter((t) => t.length > 1);
|
|
19
|
+
}
|
|
20
|
+
function signalMatchesSummary(sig, summary) {
|
|
21
|
+
const s = summary.toLowerCase();
|
|
22
|
+
const t = sig.topic.toLowerCase();
|
|
23
|
+
if (t.length > 0 && s.includes(t))
|
|
24
|
+
return true;
|
|
25
|
+
const wt = tokenize(sig.topic);
|
|
26
|
+
const ws = new Set(tokenize(summary));
|
|
27
|
+
return wt.some((x) => ws.has(x));
|
|
28
|
+
}
|
|
29
|
+
function matchInterestRefs(candidate, interest) {
|
|
30
|
+
const matched = [];
|
|
31
|
+
for (const sig of interest.signals) {
|
|
32
|
+
if (signalMatchesSummary(sig, candidate.summary)) {
|
|
33
|
+
matched.push(...sig.sourceRefs);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const byId = new Map();
|
|
37
|
+
for (const ref of matched) {
|
|
38
|
+
byId.set(ref.id, ref);
|
|
39
|
+
}
|
|
40
|
+
return [...byId.values()];
|
|
41
|
+
}
|
|
42
|
+
function scoreOutreachValue(candidate, life) {
|
|
43
|
+
if (candidate.sourceRefs.length === 0)
|
|
44
|
+
return 0;
|
|
45
|
+
const base = 0.35 + Math.min(0.35, candidate.sourceRefs.length * 0.06) + Math.min(0.2, candidate.priority * 0.02);
|
|
46
|
+
if (life.empty)
|
|
47
|
+
return Math.max(0, base - 0.15);
|
|
48
|
+
return Math.min(1, base + Math.min(0.1, life.evidenceRefCount * 0.01));
|
|
49
|
+
}
|
|
50
|
+
function scoreUserRelevance(candidate, interest) {
|
|
51
|
+
if (interest.staleness === "insufficient") {
|
|
52
|
+
return Math.min(0.35, interest.confidence);
|
|
53
|
+
}
|
|
54
|
+
const refs = matchInterestRefs(candidate, interest);
|
|
55
|
+
if (refs.length === 0)
|
|
56
|
+
return interest.confidence * 0.45;
|
|
57
|
+
return Math.min(1, interest.confidence * 0.65 + refs.length * 0.08);
|
|
58
|
+
}
|
|
59
|
+
function scoreActionability(candidate, interest, matchedInterestRefCount) {
|
|
60
|
+
if (candidate.effectClass !== "user_outreach")
|
|
61
|
+
return 0.5;
|
|
62
|
+
if (interest.staleness === "insufficient" && matchedInterestRefCount === 0)
|
|
63
|
+
return 0.55;
|
|
64
|
+
return 0.78;
|
|
65
|
+
}
|
|
66
|
+
function resolveCooldownState(input) {
|
|
67
|
+
if (input.duplicateBlocked)
|
|
68
|
+
return "duplicate";
|
|
69
|
+
if (input.cooldownBlocked)
|
|
70
|
+
return "cooling_down";
|
|
71
|
+
return "clear";
|
|
72
|
+
}
|
|
73
|
+
export function judgeOutreach(input) {
|
|
74
|
+
const decisionId = `outreach_judgment:${crypto.randomUUID()}`;
|
|
75
|
+
const deliveryResolution = resolveDeliveryTarget(input.delivery);
|
|
76
|
+
const interestRefs = matchInterestRefs(input.candidate, input.userInterest);
|
|
77
|
+
const valueScore = scoreOutreachValue(input.candidate, input.lifeEvidence);
|
|
78
|
+
const userRelevance = scoreUserRelevance(input.candidate, input.userInterest);
|
|
79
|
+
const actionability = scoreActionability(input.candidate, input.userInterest, interestRefs.length);
|
|
80
|
+
const cooldownState = resolveCooldownState(input);
|
|
81
|
+
const reasons = [];
|
|
82
|
+
if (input.candidate.sourceRefs.length === 0)
|
|
83
|
+
reasons.push("missing_source_refs");
|
|
84
|
+
if (valueScore < OUTREACH_POLICY.minValueScore)
|
|
85
|
+
reasons.push("value_score_too_low");
|
|
86
|
+
if (userRelevance < OUTREACH_POLICY.minUserRelevance &&
|
|
87
|
+
actionability < OUTREACH_POLICY.minActionabilityWhenInterestLow) {
|
|
88
|
+
reasons.push("not_interest_relevant_or_actionable");
|
|
89
|
+
}
|
|
90
|
+
if (cooldownState === "cooling_down")
|
|
91
|
+
reasons.push("cooling_down");
|
|
92
|
+
if (cooldownState === "duplicate")
|
|
93
|
+
reasons.push("duplicate");
|
|
94
|
+
if (deliveryResolution.verdict !== "target_available") {
|
|
95
|
+
reasons.push(deliveryResolution.verdict);
|
|
96
|
+
}
|
|
97
|
+
const blockingReasons = reasons.filter((r) => !isDeliveryUnavailableReason(r));
|
|
98
|
+
let verdict;
|
|
99
|
+
if (blockingReasons.length === 0) {
|
|
100
|
+
verdict = "allow";
|
|
101
|
+
}
|
|
102
|
+
else if (blockingReasons.includes("cooling_down") || blockingReasons.includes("duplicate")) {
|
|
103
|
+
verdict = "defer";
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
verdict = "deny";
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
decisionId,
|
|
110
|
+
candidateId: input.candidate.id,
|
|
111
|
+
verdict,
|
|
112
|
+
valueScore,
|
|
113
|
+
userRelevance,
|
|
114
|
+
actionability,
|
|
115
|
+
interestRefs,
|
|
116
|
+
sourceRefs: input.candidate.sourceRefs,
|
|
117
|
+
cooldownState,
|
|
118
|
+
deliveryVerdict: deliveryResolution.verdict,
|
|
119
|
+
reasons: reasons.length === 0 ? ["outreach_allowed"] : reasons,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quiet / reflection orchestration: empty evidence → empty_state; otherwise coverage-gated artifact (T2.3.3).
|
|
3
|
+
*/
|
|
4
|
+
import type { CandidateIntent } from "../types.js";
|
|
5
|
+
import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
|
|
6
|
+
import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
|
|
7
|
+
import { type QuietArtifactAck } from "../../../storage/quiet/quiet-artifact-writer.js";
|
|
8
|
+
import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
|
|
9
|
+
export interface RunSourceBackedQuietParams {
|
|
10
|
+
candidate: CandidateIntent;
|
|
11
|
+
runtime: HeartbeatRuntimeSnapshot;
|
|
12
|
+
day: string;
|
|
13
|
+
userInterestSnapshot?: UserInterestSnapshot;
|
|
14
|
+
workspaceRoot?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RunSourceBackedQuietResult {
|
|
17
|
+
result: HeartbeatCycleResult;
|
|
18
|
+
artifactAck?: QuietArtifactAck;
|
|
19
|
+
persistedRelativePath?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function runSourceBackedQuiet(params: RunSourceBackedQuietParams): Promise<RunSourceBackedQuietResult>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
|
|
2
|
+
import { writeQuietArtifact } from "../../../storage/quiet/quiet-artifact-writer.js";
|
|
3
|
+
import { persistQuietArtifactToWorkspace } from "../../../storage/quiet/persist-quiet-artifact.js";
|
|
4
|
+
import { buildEvidencePack, buildQuietNarrativeGuidance, selectInterestBasis } from "../../../guidance/evidence-guidance.js";
|
|
5
|
+
function toSourceRefFromControlPlane(r) {
|
|
6
|
+
return {
|
|
7
|
+
id: r.id,
|
|
8
|
+
kind: r.kind,
|
|
9
|
+
uri: r.uri,
|
|
10
|
+
excerptHash: r.excerptHash,
|
|
11
|
+
observedAt: r.observedAt,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function toGuidanceRef(r) {
|
|
15
|
+
return {
|
|
16
|
+
id: r.id,
|
|
17
|
+
kind: r.kind,
|
|
18
|
+
uri: r.uri,
|
|
19
|
+
excerptHash: r.excerptHash,
|
|
20
|
+
observedAt: r.observedAt,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export async function runSourceBackedQuiet(params) {
|
|
24
|
+
const { candidate, runtime, day, userInterestSnapshot, workspaceRoot } = params;
|
|
25
|
+
const empty = isLifeEvidenceSliceEmpty(runtime.lifeEvidence);
|
|
26
|
+
if (empty) {
|
|
27
|
+
const input = {
|
|
28
|
+
day,
|
|
29
|
+
kind: "empty_state",
|
|
30
|
+
title: "Quiet — no life evidence",
|
|
31
|
+
body: "No source-backed life evidence in window; narrative reflection is skipped.",
|
|
32
|
+
claims: [],
|
|
33
|
+
sourceRefs: [],
|
|
34
|
+
};
|
|
35
|
+
const ack = writeQuietArtifact(input);
|
|
36
|
+
let persistedRelativePath;
|
|
37
|
+
if (workspaceRoot) {
|
|
38
|
+
const p = await persistQuietArtifactToWorkspace(workspaceRoot, ack, input);
|
|
39
|
+
persistedRelativePath = p.relativePath;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
result: {
|
|
43
|
+
scope: "rhythm",
|
|
44
|
+
status: "intent_selected",
|
|
45
|
+
selectedIntentId: candidate.id,
|
|
46
|
+
reasons: ["quiet_empty_state", "no_fictional_narrative"],
|
|
47
|
+
},
|
|
48
|
+
artifactAck: ack,
|
|
49
|
+
persistedRelativePath,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const bundleRefs = runtime.lifeEvidence.evidenceRefs.map(toSourceRefFromControlPlane);
|
|
53
|
+
const guidanceRefs = runtime.lifeEvidence.evidenceRefs.map(toGuidanceRef);
|
|
54
|
+
const ep = buildEvidencePack(guidanceRefs);
|
|
55
|
+
if (!ep.ok) {
|
|
56
|
+
return {
|
|
57
|
+
result: {
|
|
58
|
+
scope: "rhythm",
|
|
59
|
+
status: "denied",
|
|
60
|
+
selectedIntentId: candidate.id,
|
|
61
|
+
reasons: ep.reasons,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const basis = selectInterestBasis({
|
|
66
|
+
staleness: userInterestSnapshot?.staleness ?? "insufficient",
|
|
67
|
+
confidence: userInterestSnapshot?.confidence ?? 0,
|
|
68
|
+
signalCount: userInterestSnapshot?.signals.length ?? 0,
|
|
69
|
+
});
|
|
70
|
+
const claims = ep.pack.groundedRefs.map((g, i) => ({
|
|
71
|
+
id: `fact:${g.id}`,
|
|
72
|
+
text: `Evidence-backed note ${i + 1}`,
|
|
73
|
+
claimType: "fact",
|
|
74
|
+
sourceRefs: [
|
|
75
|
+
{
|
|
76
|
+
id: g.id,
|
|
77
|
+
kind: g.kind,
|
|
78
|
+
uri: g.uri,
|
|
79
|
+
excerptHash: g.excerptHash,
|
|
80
|
+
observedAt: g.observedAt,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}));
|
|
84
|
+
const reportWrite = {
|
|
85
|
+
day,
|
|
86
|
+
kind: "daily_report",
|
|
87
|
+
title: "Quiet daily report",
|
|
88
|
+
body: `Source-backed quiet summary (${bundleRefs.length} refs).`,
|
|
89
|
+
claims,
|
|
90
|
+
sourceRefs: bundleRefs,
|
|
91
|
+
};
|
|
92
|
+
const ack = writeQuietArtifact(reportWrite);
|
|
93
|
+
const gq = buildQuietNarrativeGuidance({
|
|
94
|
+
interestBasis: basis,
|
|
95
|
+
sourceCoverage: ack.sourceCoverage,
|
|
96
|
+
outline: claims.map((c) => c.text),
|
|
97
|
+
});
|
|
98
|
+
if (gq.status === "unavailable") {
|
|
99
|
+
return {
|
|
100
|
+
result: {
|
|
101
|
+
scope: "rhythm",
|
|
102
|
+
status: "denied",
|
|
103
|
+
selectedIntentId: candidate.id,
|
|
104
|
+
reasons: gq.reasons,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
let persistedRelativePath;
|
|
109
|
+
if (workspaceRoot) {
|
|
110
|
+
const p = await persistQuietArtifactToWorkspace(workspaceRoot, ack, reportWrite);
|
|
111
|
+
persistedRelativePath = p.relativePath;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
result: {
|
|
115
|
+
scope: "rhythm",
|
|
116
|
+
status: "intent_selected",
|
|
117
|
+
selectedIntentId: candidate.id,
|
|
118
|
+
reasons: ["quiet_artifact_written", ...gq.hints.slice(0, 2)],
|
|
119
|
+
},
|
|
120
|
+
artifactAck: ack,
|
|
121
|
+
persistedRelativePath,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps calendar rhythm policy + continuity into planner-facing window slice (T2.1.3).
|
|
3
|
+
* Control-plane owns allowedIntentKinds; state never emits them (T2.1.2 boundary).
|
|
4
|
+
*/
|
|
5
|
+
import type { ContinuitySnapshot, IntentKind } from "../types.js";
|
|
6
|
+
import type { RhythmPolicy } from "./rhythm-policy.js";
|
|
7
|
+
export interface PlannerRhythmWindowSlice {
|
|
8
|
+
windowId: string;
|
|
9
|
+
allowedIntentKinds: IntentKind[];
|
|
10
|
+
quietBias: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Derive allowed intent kinds and quiet bias for candidate planning.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildPlannerRhythmWindow(now: string, continuity: ContinuitySnapshot, policy: RhythmPolicy): PlannerRhythmWindowSlice;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { selectRhythmWindow } from "./select-window.js";
|
|
2
|
+
const ALL_INTENT_KINDS = [
|
|
3
|
+
"work",
|
|
4
|
+
"exploration",
|
|
5
|
+
"social",
|
|
6
|
+
"quiet",
|
|
7
|
+
"reflection",
|
|
8
|
+
"outreach",
|
|
9
|
+
"maintenance",
|
|
10
|
+
];
|
|
11
|
+
function allowedForPaused() {
|
|
12
|
+
return ["maintenance"];
|
|
13
|
+
}
|
|
14
|
+
function allowedForMaintenanceOnly() {
|
|
15
|
+
return ["work", "maintenance"];
|
|
16
|
+
}
|
|
17
|
+
function allowedForActiveWindow(windowId) {
|
|
18
|
+
if (windowId.includes("work")) {
|
|
19
|
+
return ["work", "exploration", "maintenance", "reflection", "outreach", "social", "quiet"];
|
|
20
|
+
}
|
|
21
|
+
if (windowId.includes("social")) {
|
|
22
|
+
return ["social", "exploration", "work", "maintenance", "reflection", "outreach", "quiet"];
|
|
23
|
+
}
|
|
24
|
+
if (windowId.includes("reflection")) {
|
|
25
|
+
return ["reflection", "work", "maintenance", "exploration", "social", "outreach", "quiet"];
|
|
26
|
+
}
|
|
27
|
+
return ALL_INTENT_KINDS;
|
|
28
|
+
}
|
|
29
|
+
function mergeQuietBias(decision, continuity, windowIsQuiet) {
|
|
30
|
+
return windowIsQuiet || decision.topLevelMode === "quiet" || continuity.mode === "quiet";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derive allowed intent kinds and quiet bias for candidate planning.
|
|
34
|
+
*/
|
|
35
|
+
export function buildPlannerRhythmWindow(now, continuity, policy) {
|
|
36
|
+
const decision = selectRhythmWindow(now, continuity, policy);
|
|
37
|
+
const window = policy.windows.find((w) => w.id === decision.windowId) ?? policy.windows[0];
|
|
38
|
+
const windowIsQuiet = window.mode === "quiet";
|
|
39
|
+
const quietBias = mergeQuietBias(decision, continuity, windowIsQuiet);
|
|
40
|
+
let allowed;
|
|
41
|
+
if (decision.topLevelMode === "paused_for_interrupt") {
|
|
42
|
+
allowed = allowedForPaused();
|
|
43
|
+
}
|
|
44
|
+
else if (decision.topLevelMode === "maintenance_only") {
|
|
45
|
+
allowed = allowedForMaintenanceOnly();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
/** Calendar quiet sets `quietBias` only; candidate kinds stay window-biased (guards enforce quiet suppression). */
|
|
49
|
+
allowed = allowedForActiveWindow(window.id);
|
|
50
|
+
}
|
|
51
|
+
return { windowId: decision.windowId, allowedIntentKinds: allowed, quietBias };
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges state-system RhythmPolicySnapshot fields into control-plane RhythmPolicy (T2.1.2).
|
|
3
|
+
*
|
|
4
|
+
* State never emits window decisions or allowedIntentKinds; control-plane owns
|
|
5
|
+
* window geometry used by selectRhythmWindow(). Callers pass a pick of the DB read model.
|
|
6
|
+
*/
|
|
7
|
+
import type { RhythmPolicy } from "./rhythm-policy.js";
|
|
8
|
+
/** Subset of `RhythmPolicySnapshot` used for window derivation (no storage import from core). */
|
|
9
|
+
export interface RhythmPolicySnapshotBridgeInput {
|
|
10
|
+
quietEnabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reject drifted snapshot shapes that smuggle control-plane decision fields.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertRhythmPolicySnapshotContract(snapshot: Record<string, unknown>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic default windows from policy knobs (quiet hour tail when quietEnabled).
|
|
18
|
+
*/
|
|
19
|
+
export declare function rhythmPolicySnapshotToRhythmPolicy(snapshot: RhythmPolicySnapshotBridgeInput): RhythmPolicy;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const FORBIDDEN_SNAPSHOT_KEYS = ["allowedIntentKinds", "windowDecision", "rhythmWindow"];
|
|
2
|
+
/**
|
|
3
|
+
* Reject drifted snapshot shapes that smuggle control-plane decision fields.
|
|
4
|
+
*/
|
|
5
|
+
export function assertRhythmPolicySnapshotContract(snapshot) {
|
|
6
|
+
for (const key of FORBIDDEN_SNAPSHOT_KEYS) {
|
|
7
|
+
if (key in snapshot) {
|
|
8
|
+
throw new Error(`rhythm_policy_snapshot_field_drift:${key}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Deterministic default windows from policy knobs (quiet hour tail when quietEnabled).
|
|
14
|
+
*/
|
|
15
|
+
export function rhythmPolicySnapshotToRhythmPolicy(snapshot) {
|
|
16
|
+
assertRhythmPolicySnapshotContract(snapshot);
|
|
17
|
+
if (snapshot.quietEnabled) {
|
|
18
|
+
return {
|
|
19
|
+
timezone: "UTC",
|
|
20
|
+
quietSuppressionEnabled: true,
|
|
21
|
+
windows: [
|
|
22
|
+
{ id: "w-work", startMinute: 0, endMinute: 480, mode: "active" },
|
|
23
|
+
{ id: "w-social", startMinute: 480, endMinute: 960, mode: "active" },
|
|
24
|
+
{ id: "w-reflection", startMinute: 960, endMinute: 1200, mode: "active" },
|
|
25
|
+
{ id: "w-quiet", startMinute: 1200, endMinute: 1440, mode: "quiet" },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
timezone: "UTC",
|
|
31
|
+
quietSuppressionEnabled: false,
|
|
32
|
+
windows: [{ id: "w-open", startMinute: 0, endMinute: 1440, mode: "active" }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export type TopLevelMode = "active" | "quiet" | "maintenance_only" | "paused_for_interrupt";
|
|
2
|
-
|
|
2
|
+
/** Control-plane candidate kinds; includes `quiet` for quiet-window–biased intents (L0 alignment). */
|
|
3
|
+
export type IntentKind = "work" | "exploration" | "social" | "quiet" | "reflection" | "outreach" | "maintenance";
|
|
3
4
|
export type DecisionBasis = "rule_only" | "score_based" | "model_assisted";
|
|
4
5
|
export type GuardVerdict = "allow" | "defer" | "deny" | "escalate";
|
|
6
|
+
/** Minimal source ref for planner / guards (matches state-system `SourceRef` subset). */
|
|
7
|
+
export interface ControlPlaneSourceRef {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
|
|
10
|
+
uri: string;
|
|
11
|
+
excerptHash?: string;
|
|
12
|
+
observedAt?: string;
|
|
13
|
+
}
|
|
5
14
|
export interface ContinuitySnapshot {
|
|
6
15
|
mode: TopLevelMode;
|
|
7
16
|
currentWindowId: string;
|
|
@@ -19,6 +28,7 @@ export interface ContinuitySnapshot {
|
|
|
19
28
|
awaitingUserInput?: boolean;
|
|
20
29
|
riskSuppressed?: boolean;
|
|
21
30
|
}
|
|
31
|
+
export type CandidateEffectClass = "external_platform_action" | "connector_action" | "memory_curation" | "narrative_reflection" | "user_outreach" | "maintenance" | "no_effect";
|
|
22
32
|
export interface CandidateIntent {
|
|
23
33
|
id: string;
|
|
24
34
|
kind: IntentKind;
|
|
@@ -26,7 +36,11 @@ export interface CandidateIntent {
|
|
|
26
36
|
source: "tick" | "interrupt" | "obligation" | "quiet_plan";
|
|
27
37
|
platformId?: string;
|
|
28
38
|
summary: string;
|
|
29
|
-
effectClass:
|
|
39
|
+
effectClass: CandidateEffectClass;
|
|
40
|
+
/** Required for source-backed guard; may be empty when planner expects hard-guard deny. */
|
|
41
|
+
sourceRefs: ControlPlaneSourceRef[];
|
|
42
|
+
/** Dedupe / cooldown key; defaults to stable fingerprint in guard layer when omitted. */
|
|
43
|
+
idempotencyKey?: string;
|
|
30
44
|
}
|
|
31
45
|
export interface GuardEvaluation {
|
|
32
46
|
verdict: GuardVerdict;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic GuidanceDraftPort implementation for contract tests and packaged runtime.
|
|
3
|
+
* Does not claim user-visible delivery when wordingMode is not_sent_fallback_candidate (T6.2.1 / ADR-004).
|
|
4
|
+
*/
|
|
5
|
+
import { type GuidanceDraftPort, type OutreachDraftRequest } from "./outreach-draft-schema.js";
|
|
6
|
+
export declare function draftOutreachMessage(request: OutreachDraftRequest): ReturnType<GuidanceDraftPort["draftOutreachMessage"]>;
|
|
7
|
+
export declare function createDraftOutreachMessagePort(): GuidanceDraftPort;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic GuidanceDraftPort implementation for contract tests and packaged runtime.
|
|
3
|
+
* Does not claim user-visible delivery when wordingMode is not_sent_fallback_candidate (T6.2.1 / ADR-004).
|
|
4
|
+
*/
|
|
5
|
+
import { safeParseOutreachDraftRequest } from "./outreach-draft-schema.js";
|
|
6
|
+
function baseDraftText(request) {
|
|
7
|
+
const ids = request.sourceRefs.map((s) => s.id).join(",");
|
|
8
|
+
return `draft:${request.candidateId}:grounded:${ids}`;
|
|
9
|
+
}
|
|
10
|
+
export async function draftOutreachMessage(request) {
|
|
11
|
+
const parsed = safeParseOutreachDraftRequest(request);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
return { status: "unavailable", reasons: ["outreach_draft_schema_invalid"] };
|
|
14
|
+
}
|
|
15
|
+
const r = parsed.data;
|
|
16
|
+
if (r.judgmentVerdict !== "allow") {
|
|
17
|
+
return { status: "unavailable", reasons: ["hard_decision_not_allow"] };
|
|
18
|
+
}
|
|
19
|
+
if (r.sourceRefs.length === 0) {
|
|
20
|
+
return { status: "unavailable", reasons: ["missing_resolved_source_refs"] };
|
|
21
|
+
}
|
|
22
|
+
const wording = r.deliveryContext.wordingMode;
|
|
23
|
+
if (wording === "sendable") {
|
|
24
|
+
return {
|
|
25
|
+
status: "ready",
|
|
26
|
+
draft: {
|
|
27
|
+
text: `${baseDraftText(r)};wording=sendable`,
|
|
28
|
+
deliveryWording: "sendable",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: "ready",
|
|
34
|
+
draft: {
|
|
35
|
+
text: `Not sent to the user (candidate only). Delivery state: ${r.deliveryContext.deliveryVerdict}. ${baseDraftText(r)}`,
|
|
36
|
+
deliveryWording: "not_sent_fallback_candidate",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createDraftOutreachMessagePort() {
|
|
41
|
+
return { draftOutreachMessage };
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence pack assembly, interest-basis selection, and Quiet narrative gate (T6.1.2 / ADR-004).
|
|
3
|
+
* Guidance does not own delivery or judgment; callers pass already-resolved refs.
|
|
4
|
+
*/
|
|
5
|
+
import type { GuidanceSourceRef } from "./outreach-draft-schema.js";
|
|
6
|
+
import type { UserInterestStaleness } from "../storage/user-interest/types.js";
|
|
7
|
+
import type { SourceCoverage } from "../storage/snapshots/types.js";
|
|
8
|
+
export type SourceCoveragePolicy = "strict" | "lenient";
|
|
9
|
+
export interface EvidencePack {
|
|
10
|
+
groundedRefs: GuidanceSourceRef[];
|
|
11
|
+
unresolvedIds: string[];
|
|
12
|
+
sensitiveBlocked: boolean;
|
|
13
|
+
policy: SourceCoveragePolicy;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildEvidencePack(refs: GuidanceSourceRef[], opts?: {
|
|
16
|
+
policy?: SourceCoveragePolicy;
|
|
17
|
+
}): {
|
|
18
|
+
ok: true;
|
|
19
|
+
pack: EvidencePack;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
reasons: string[];
|
|
23
|
+
};
|
|
24
|
+
export type InterestBasisMode = "evidence_only" | "interest_augmented" | "unavailable";
|
|
25
|
+
export declare function selectInterestBasis(input: {
|
|
26
|
+
staleness: UserInterestStaleness;
|
|
27
|
+
confidence: number;
|
|
28
|
+
signalCount: number;
|
|
29
|
+
}): InterestBasisMode;
|
|
30
|
+
export declare function buildQuietNarrativeGuidance(input: {
|
|
31
|
+
interestBasis: InterestBasisMode;
|
|
32
|
+
sourceCoverage: Pick<SourceCoverage, "coverageRatio" | "unsupportedClaims">;
|
|
33
|
+
outline: string[];
|
|
34
|
+
}): {
|
|
35
|
+
status: "ready";
|
|
36
|
+
hints: string[];
|
|
37
|
+
} | {
|
|
38
|
+
status: "unavailable";
|
|
39
|
+
reasons: string[];
|
|
40
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function buildEvidencePack(refs, opts) {
|
|
2
|
+
const policy = opts?.policy ?? "strict";
|
|
3
|
+
const unresolvedIds = [];
|
|
4
|
+
let sensitiveBlocked = false;
|
|
5
|
+
const grounded = [];
|
|
6
|
+
for (const r of refs) {
|
|
7
|
+
if (!r.uri?.trim()) {
|
|
8
|
+
unresolvedIds.push(r.id);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (r.uri.includes("credential") || r.uri.includes("secret")) {
|
|
12
|
+
sensitiveBlocked = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
grounded.push(r);
|
|
16
|
+
}
|
|
17
|
+
if (policy === "strict" && unresolvedIds.length > 0) {
|
|
18
|
+
return { ok: false, reasons: ["unresolved_source_refs", ...unresolvedIds.slice(0, 3)] };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
pack: {
|
|
23
|
+
groundedRefs: grounded,
|
|
24
|
+
unresolvedIds,
|
|
25
|
+
sensitiveBlocked,
|
|
26
|
+
policy,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function selectInterestBasis(input) {
|
|
31
|
+
if (input.staleness === "insufficient" || input.confidence < 0.15) {
|
|
32
|
+
return input.signalCount > 0 ? "evidence_only" : "unavailable";
|
|
33
|
+
}
|
|
34
|
+
if (input.staleness === "stale") {
|
|
35
|
+
return "evidence_only";
|
|
36
|
+
}
|
|
37
|
+
return "interest_augmented";
|
|
38
|
+
}
|
|
39
|
+
export function buildQuietNarrativeGuidance(input) {
|
|
40
|
+
if (input.interestBasis === "unavailable" && input.sourceCoverage.coverageRatio < 0.25) {
|
|
41
|
+
return { status: "unavailable", reasons: ["quiet_guidance_insufficient_interest_and_coverage"] };
|
|
42
|
+
}
|
|
43
|
+
if (input.sourceCoverage.unsupportedClaims.length > 0) {
|
|
44
|
+
return { status: "unavailable", reasons: ["quiet_guidance_unsupported_claims"] };
|
|
45
|
+
}
|
|
46
|
+
const hints = [
|
|
47
|
+
...input.outline.slice(0, 3).map((line) => `hint:${line}`),
|
|
48
|
+
`basis:${input.interestBasis}`,
|
|
49
|
+
`coverage:${input.sourceCoverage.coverageRatio.toFixed(2)}`,
|
|
50
|
+
];
|
|
51
|
+
return { status: "ready", hints };
|
|
52
|
+
}
|
|
@@ -6,3 +6,6 @@ export * from "./fallback.js";
|
|
|
6
6
|
export * from "./template-registry.js";
|
|
7
7
|
export * from "./review-workflow.js";
|
|
8
8
|
export * from "./guidance-assembler.js";
|
|
9
|
+
export * from "./outreach-draft-schema.js";
|
|
10
|
+
export * from "./draft-outreach-message.js";
|
|
11
|
+
export * from "./evidence-guidance.js";
|
|
@@ -6,3 +6,6 @@ export * from "./fallback.js";
|
|
|
6
6
|
export * from "./template-registry.js";
|
|
7
7
|
export * from "./review-workflow.js";
|
|
8
8
|
export * from "./guidance-assembler.js";
|
|
9
|
+
export * from "./outreach-draft-schema.js";
|
|
10
|
+
export * from "./draft-outreach-message.js";
|
|
11
|
+
export * from "./evidence-guidance.js";
|