@haaaiawd/second-nature 0.1.8 → 0.1.10
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 +281 -69
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -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 +162 -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/repositories/credential-repository.js +12 -1
- 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 +47 -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
- package/workspace-ops-bridge.js +78 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps control-plane judgment + delivery resolution into guidance OutreachDraftRequest (T6.2.1).
|
|
3
|
+
* Aligns with control-plane-system.detail §3.9 buildOutreachDraftRequest.
|
|
4
|
+
*/
|
|
5
|
+
import * as crypto from "node:crypto";
|
|
6
|
+
function inferRhythmWindowKind(windowId) {
|
|
7
|
+
const id = windowId.toLowerCase();
|
|
8
|
+
if (id.includes("work"))
|
|
9
|
+
return "work";
|
|
10
|
+
if (id.includes("social"))
|
|
11
|
+
return "social";
|
|
12
|
+
if (id.includes("quiet"))
|
|
13
|
+
return "quiet";
|
|
14
|
+
if (id.includes("reflect"))
|
|
15
|
+
return "reflection";
|
|
16
|
+
if (id.includes("explore"))
|
|
17
|
+
return "exploration";
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function toGuidanceRefs(refs) {
|
|
21
|
+
return refs.map((r) => ({
|
|
22
|
+
id: r.id,
|
|
23
|
+
kind: r.kind,
|
|
24
|
+
uri: r.uri,
|
|
25
|
+
excerptHash: r.excerptHash,
|
|
26
|
+
observedAt: r.observedAt,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
function mapDeliveryVerdict(verdict) {
|
|
30
|
+
switch (verdict) {
|
|
31
|
+
case "target_available":
|
|
32
|
+
return "target_available";
|
|
33
|
+
case "target_none":
|
|
34
|
+
return "target_none";
|
|
35
|
+
case "channel_missing":
|
|
36
|
+
return "channel_missing";
|
|
37
|
+
case "host_unsupported":
|
|
38
|
+
return "host_unsupported";
|
|
39
|
+
default:
|
|
40
|
+
return "host_unsupported";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function buildOutreachDraftRequest(candidate, judgment, snapshot, delivery) {
|
|
44
|
+
const sceneType = delivery.verdict === "target_available" ? "outreach" : "fallback_candidate";
|
|
45
|
+
const riskLevel = delivery.verdict === "target_available" ? "medium" : "low";
|
|
46
|
+
return {
|
|
47
|
+
requestId: `outreach_draft_request:${crypto.randomUUID()}`,
|
|
48
|
+
sceneType,
|
|
49
|
+
runtimeScope: "rhythm",
|
|
50
|
+
rhythmWindowKind: inferRhythmWindowKind(snapshot.rhythmWindow.windowId),
|
|
51
|
+
riskLevel,
|
|
52
|
+
sourceRefs: toGuidanceRefs(judgment.sourceRefs),
|
|
53
|
+
decisionId: judgment.decisionId,
|
|
54
|
+
candidateId: candidate.id,
|
|
55
|
+
judgmentVerdict: judgment.verdict,
|
|
56
|
+
valueScore: judgment.valueScore,
|
|
57
|
+
interestRefs: toGuidanceRefs(judgment.interestRefs),
|
|
58
|
+
deliveryContext: {
|
|
59
|
+
deliveryVerdict: mapDeliveryVerdict(delivery.verdict),
|
|
60
|
+
wordingMode: delivery.verdict === "target_available" ? "sendable" : "not_sent_fallback_candidate",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves OpenClaw-visible delivery target from host capability snapshot (T2.3.1 / ADR-007).
|
|
3
|
+
*
|
|
4
|
+
* Core logic: explicit/last targets require channel materialization; `none` is a first-class verdict.
|
|
5
|
+
* Test coverage: tests/unit/core/outreach-judgment.test.ts
|
|
6
|
+
*/
|
|
7
|
+
export type DeliveryHostTarget = "none" | "last" | "explicit";
|
|
8
|
+
export interface DeliveryCapabilitySnapshot {
|
|
9
|
+
/** Raw host value; may be empty string before normalization. */
|
|
10
|
+
target?: DeliveryHostTarget | string | null;
|
|
11
|
+
channel?: string | null;
|
|
12
|
+
recipient?: string | null;
|
|
13
|
+
lastKnownVisibleChannel?: string | null;
|
|
14
|
+
/** When true, host reports unsupported delivery surface (maps to host_unsupported verdict). */
|
|
15
|
+
hostUnsupported?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type DeliveryTargetVerdict = "target_none" | "channel_missing" | "target_available" | "host_unsupported";
|
|
18
|
+
export interface DeliveryTargetResolution {
|
|
19
|
+
verdict: DeliveryTargetVerdict;
|
|
20
|
+
target?: DeliveryHostTarget;
|
|
21
|
+
channel?: string;
|
|
22
|
+
recipient?: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function resolveDeliveryTarget(snapshot: DeliveryCapabilitySnapshot): DeliveryTargetResolution;
|
|
26
|
+
export declare function isDeliveryUnavailableReason(reason: string): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves OpenClaw-visible delivery target from host capability snapshot (T2.3.1 / ADR-007).
|
|
3
|
+
*
|
|
4
|
+
* Core logic: explicit/last targets require channel materialization; `none` is a first-class verdict.
|
|
5
|
+
* Test coverage: tests/unit/core/outreach-judgment.test.ts
|
|
6
|
+
*/
|
|
7
|
+
export function resolveDeliveryTarget(snapshot) {
|
|
8
|
+
if (snapshot.hostUnsupported) {
|
|
9
|
+
return {
|
|
10
|
+
verdict: "host_unsupported",
|
|
11
|
+
target: snapshot.target && String(snapshot.target).trim() !== "" && snapshot.target !== "none"
|
|
12
|
+
? snapshot.target
|
|
13
|
+
: "none",
|
|
14
|
+
reason: "host_delivery_surface_unsupported",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const raw = snapshot.target;
|
|
18
|
+
if (raw === undefined || raw === null || String(raw).trim() === "" || raw === "none") {
|
|
19
|
+
return {
|
|
20
|
+
verdict: "target_none",
|
|
21
|
+
target: "none",
|
|
22
|
+
reason: "heartbeat_run_without_user_visible_delivery",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (raw === "explicit") {
|
|
26
|
+
const ch = (snapshot.channel ?? "").trim();
|
|
27
|
+
const rec = (snapshot.recipient ?? "").trim();
|
|
28
|
+
if (!ch || !rec) {
|
|
29
|
+
return {
|
|
30
|
+
verdict: "channel_missing",
|
|
31
|
+
target: "explicit",
|
|
32
|
+
reason: "explicit_delivery_requires_channel_and_recipient",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
verdict: "target_available",
|
|
37
|
+
target: "explicit",
|
|
38
|
+
channel: ch,
|
|
39
|
+
recipient: rec,
|
|
40
|
+
reason: "delivery_target_available",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (raw === "last") {
|
|
44
|
+
const lastCh = (snapshot.lastKnownVisibleChannel ?? "").trim();
|
|
45
|
+
if (!lastCh) {
|
|
46
|
+
return {
|
|
47
|
+
verdict: "channel_missing",
|
|
48
|
+
target: "last",
|
|
49
|
+
reason: "last_target_has_no_known_visible_channel",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
verdict: "target_available",
|
|
54
|
+
target: "last",
|
|
55
|
+
channel: lastCh,
|
|
56
|
+
recipient: snapshot.recipient?.trim() || undefined,
|
|
57
|
+
reason: "delivery_target_available",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
verdict: "target_available",
|
|
62
|
+
target: raw,
|
|
63
|
+
channel: snapshot.channel?.trim() || snapshot.lastKnownVisibleChannel?.trim() || undefined,
|
|
64
|
+
recipient: snapshot.recipient?.trim() || undefined,
|
|
65
|
+
reason: "delivery_target_available",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function isDeliveryUnavailableReason(reason) {
|
|
69
|
+
return reason === "target_none" || reason === "channel_missing" || reason === "host_unsupported";
|
|
70
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User outreach dispatch path: judgment → draft → host delivery → attempt + operator fallback (T2.3.2).
|
|
3
|
+
* Mirrors control-plane-system.detail §3.9 dispatchAllowedIntent user_outreach branch.
|
|
4
|
+
*/
|
|
5
|
+
import type { GuidanceDraftPort } from "../../../guidance/outreach-draft-schema.js";
|
|
6
|
+
import type { CandidateIntent } from "../types.js";
|
|
7
|
+
import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
|
|
8
|
+
import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
|
|
9
|
+
import type { StateDatabase } from "../../../storage/db/index.js";
|
|
10
|
+
import type { SourceRef } from "../../../storage/life-evidence/types.js";
|
|
11
|
+
import { type JudgeOutreachInput } from "./judge-outreach.js";
|
|
12
|
+
import { type DeliveryTargetResolution } from "./delivery-target.js";
|
|
13
|
+
export interface OpenClawDeliverySendResult {
|
|
14
|
+
id: string;
|
|
15
|
+
status: "sent" | "failed" | "dropped_by_host_policy";
|
|
16
|
+
errorClass?: string;
|
|
17
|
+
messageId?: string;
|
|
18
|
+
/** Host-reported delivery proof when messageId is absent (T4.3.1). */
|
|
19
|
+
hostProofRef?: SourceRef;
|
|
20
|
+
}
|
|
21
|
+
export interface OpenClawDeliveryPort {
|
|
22
|
+
sendDeliveryRequest(input: {
|
|
23
|
+
decisionId: string;
|
|
24
|
+
target: NonNullable<DeliveryTargetResolution["target"]>;
|
|
25
|
+
channel: string;
|
|
26
|
+
recipient?: string;
|
|
27
|
+
message: string;
|
|
28
|
+
sourceRefs: CandidateIntent["sourceRefs"];
|
|
29
|
+
}): Promise<OpenClawDeliverySendResult>;
|
|
30
|
+
}
|
|
31
|
+
export declare function dispatchUserOutreachIntent(input: {
|
|
32
|
+
candidate: CandidateIntent;
|
|
33
|
+
snapshot: HeartbeatRuntimeSnapshot;
|
|
34
|
+
judgeInput: Omit<JudgeOutreachInput, "candidate">;
|
|
35
|
+
guidance: GuidanceDraftPort;
|
|
36
|
+
delivery: OpenClawDeliveryPort;
|
|
37
|
+
state: StateDatabase;
|
|
38
|
+
}): Promise<HeartbeatCycleResult>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { writeDeliveryAttempt } from "../../../storage/delivery/write-delivery-attempt.js";
|
|
2
|
+
import { writeOperatorFallback } from "../../../storage/fallback/write-operator-fallback.js";
|
|
3
|
+
import { judgeOutreach } from "./judge-outreach.js";
|
|
4
|
+
import { resolveDeliveryTarget } from "./delivery-target.js";
|
|
5
|
+
import { buildOutreachDraftRequest } from "./build-outreach-draft-request.js";
|
|
6
|
+
function toSourceRefs(refs) {
|
|
7
|
+
return refs.map((r) => ({ ...r }));
|
|
8
|
+
}
|
|
9
|
+
function hasDeliveryProof(attempt) {
|
|
10
|
+
return Boolean(attempt.messageId?.trim()) || Boolean(attempt.hostProofRef);
|
|
11
|
+
}
|
|
12
|
+
function operatorReasonForUnavailable(verdict) {
|
|
13
|
+
if (verdict === "target_none")
|
|
14
|
+
return "target_none";
|
|
15
|
+
if (verdict === "channel_missing")
|
|
16
|
+
return "channel_missing";
|
|
17
|
+
return "host_unsupported";
|
|
18
|
+
}
|
|
19
|
+
export async function dispatchUserOutreachIntent(input) {
|
|
20
|
+
const { candidate, snapshot, judgeInput, guidance, delivery, state } = input;
|
|
21
|
+
const judgment = judgeOutreach({ ...judgeInput, candidate });
|
|
22
|
+
if (judgment.verdict !== "allow") {
|
|
23
|
+
return {
|
|
24
|
+
scope: "rhythm",
|
|
25
|
+
status: judgment.verdict === "defer" ? "deferred" : "denied",
|
|
26
|
+
selectedIntentId: candidate.id,
|
|
27
|
+
reasons: judgment.reasons,
|
|
28
|
+
decisionId: judgment.decisionId,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const deliveryResolution = resolveDeliveryTarget(judgeInput.delivery);
|
|
32
|
+
if (deliveryResolution.verdict !== "target_available") {
|
|
33
|
+
const req = buildOutreachDraftRequest(candidate, judgment, snapshot, deliveryResolution);
|
|
34
|
+
const draft = await guidance.draftOutreachMessage(req);
|
|
35
|
+
const fb = await writeOperatorFallback(state, {
|
|
36
|
+
reason: operatorReasonForUnavailable(deliveryResolution.verdict),
|
|
37
|
+
decisionId: judgment.decisionId,
|
|
38
|
+
sourceRefs: toSourceRefs(judgment.sourceRefs),
|
|
39
|
+
candidateMessage: draft.status === "ready" ? draft.draft.text : undefined,
|
|
40
|
+
nextStep: "resolve_delivery_target_or_retry_after_host_update",
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
scope: "rhythm",
|
|
44
|
+
status: "delivery_unavailable",
|
|
45
|
+
selectedIntentId: candidate.id,
|
|
46
|
+
reasons: [deliveryResolution.reason],
|
|
47
|
+
decisionId: judgment.decisionId,
|
|
48
|
+
fallbackRef: fb.fallbackRef,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const req = buildOutreachDraftRequest(candidate, judgment, snapshot, deliveryResolution);
|
|
52
|
+
const draft = await guidance.draftOutreachMessage(req);
|
|
53
|
+
if (draft.status !== "ready") {
|
|
54
|
+
return {
|
|
55
|
+
scope: "rhythm",
|
|
56
|
+
status: "denied",
|
|
57
|
+
selectedIntentId: candidate.id,
|
|
58
|
+
reasons: draft.reasons,
|
|
59
|
+
decisionId: judgment.decisionId,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const attempt = await delivery.sendDeliveryRequest({
|
|
63
|
+
decisionId: judgment.decisionId,
|
|
64
|
+
target: deliveryResolution.target,
|
|
65
|
+
channel: deliveryResolution.channel,
|
|
66
|
+
recipient: deliveryResolution.recipient,
|
|
67
|
+
message: draft.draft.text,
|
|
68
|
+
sourceRefs: judgment.sourceRefs,
|
|
69
|
+
});
|
|
70
|
+
if (attempt.status !== "sent" || !hasDeliveryProof(attempt)) {
|
|
71
|
+
const fb = await writeOperatorFallback(state, {
|
|
72
|
+
reason: "delivery_failed",
|
|
73
|
+
decisionId: judgment.decisionId,
|
|
74
|
+
sourceRefs: toSourceRefs(judgment.sourceRefs),
|
|
75
|
+
candidateMessage: draft.draft.text,
|
|
76
|
+
nextStep: "review_delivery_audit_and_host_capability",
|
|
77
|
+
});
|
|
78
|
+
const hostReportedSentWithoutProof = attempt.status === "sent" && !hasDeliveryProof(attempt);
|
|
79
|
+
await writeDeliveryAttempt(state, {
|
|
80
|
+
attemptId: attempt.id,
|
|
81
|
+
decisionId: judgment.decisionId,
|
|
82
|
+
target: deliveryResolution.target,
|
|
83
|
+
channel: deliveryResolution.channel,
|
|
84
|
+
status: attempt.status === "dropped_by_host_policy" ? "dropped_by_host_policy" : "failed",
|
|
85
|
+
errorClass: hostReportedSentWithoutProof
|
|
86
|
+
? "delivery_proof_missing"
|
|
87
|
+
: attempt.errorClass ?? attempt.status,
|
|
88
|
+
fallbackRef: fb.fallbackRef,
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
scope: "rhythm",
|
|
92
|
+
status: "delivery_unavailable",
|
|
93
|
+
selectedIntentId: candidate.id,
|
|
94
|
+
reasons: hostReportedSentWithoutProof
|
|
95
|
+
? ["delivery_failed", "delivery_proof_missing"]
|
|
96
|
+
: ["delivery_failed", attempt.status],
|
|
97
|
+
decisionId: judgment.decisionId,
|
|
98
|
+
deliveryAttemptId: attempt.id,
|
|
99
|
+
fallbackRef: fb.fallbackRef,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
await writeDeliveryAttempt(state, {
|
|
103
|
+
attemptId: attempt.id,
|
|
104
|
+
decisionId: judgment.decisionId,
|
|
105
|
+
target: deliveryResolution.target,
|
|
106
|
+
channel: deliveryResolution.channel,
|
|
107
|
+
status: "sent",
|
|
108
|
+
messageId: attempt.messageId?.trim(),
|
|
109
|
+
hostProofRef: attempt.hostProofRef,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
scope: "rhythm",
|
|
113
|
+
status: "intent_selected",
|
|
114
|
+
selectedIntentId: candidate.id,
|
|
115
|
+
reasons: ["outreach_sent"],
|
|
116
|
+
decisionId: judgment.decisionId,
|
|
117
|
+
deliveryAttemptId: attempt.id,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SnapshotInputs } from "../heartbeat/snapshot-builder.js";
|
|
2
|
+
import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
|
|
3
|
+
import type { CandidateIntent } from "../types.js";
|
|
4
|
+
import type { JudgeOutreachInput, JudgeOutreachUserInterest } from "./judge-outreach.js";
|
|
5
|
+
import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
|
|
6
|
+
export declare function userInterestSnapshotToJudge(snapshot?: UserInterestSnapshot): JudgeOutreachUserInterest;
|
|
7
|
+
export declare function buildJudgeOutreachInputFromSnapshot(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs): Omit<JudgeOutreachInput, "candidate">;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
|
|
2
|
+
function toControlPlaneRefs(refs) {
|
|
3
|
+
return refs.map((r) => ({
|
|
4
|
+
id: r.id,
|
|
5
|
+
kind: r.kind,
|
|
6
|
+
uri: r.uri,
|
|
7
|
+
excerptHash: r.excerptHash,
|
|
8
|
+
observedAt: r.observedAt,
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
export function userInterestSnapshotToJudge(snapshot) {
|
|
12
|
+
if (!snapshot) {
|
|
13
|
+
return { staleness: "insufficient", confidence: 0, signals: [], sourceRefs: [] };
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
staleness: snapshot.staleness,
|
|
17
|
+
confidence: snapshot.confidence,
|
|
18
|
+
signals: snapshot.signals.map((s) => ({
|
|
19
|
+
topic: s.topic,
|
|
20
|
+
confidence: s.confidence,
|
|
21
|
+
sourceRefs: s.sourceRefs.map((r) => ({
|
|
22
|
+
id: r.id,
|
|
23
|
+
kind: r.kind,
|
|
24
|
+
uri: r.uri,
|
|
25
|
+
excerptHash: r.excerptHash,
|
|
26
|
+
observedAt: r.observedAt,
|
|
27
|
+
})),
|
|
28
|
+
})),
|
|
29
|
+
sourceRefs: toControlPlaneRefs(snapshot.sourceRefs),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function buildJudgeOutreachInputFromSnapshot(intent, runtime, inputs) {
|
|
33
|
+
const delivery = inputs.deliveryCapability ?? { target: "none" };
|
|
34
|
+
const key = intent.idempotencyKey ?? intent.id;
|
|
35
|
+
return {
|
|
36
|
+
userInterest: userInterestSnapshotToJudge(inputs.userInterestSnapshot),
|
|
37
|
+
lifeEvidence: {
|
|
38
|
+
empty: isLifeEvidenceSliceEmpty(runtime.lifeEvidence),
|
|
39
|
+
evidenceRefCount: runtime.lifeEvidence.evidenceRefs.length,
|
|
40
|
+
},
|
|
41
|
+
delivery,
|
|
42
|
+
duplicateBlocked: runtime.hardGuards.hasDuplicateIntent(key),
|
|
43
|
+
cooldownBlocked: !runtime.hardGuards.isOutreachCooldownClear(key),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CandidateIntent, ControlPlaneSourceRef } from "../types.js";
|
|
2
|
+
import { type DeliveryCapabilitySnapshot, type DeliveryTargetResolution } from "./delivery-target.js";
|
|
3
|
+
export type OutreachJudgmentVerdict = "allow" | "deny" | "defer";
|
|
4
|
+
export type CooldownState = "clear" | "cooling_down" | "duplicate";
|
|
5
|
+
export interface JudgeOutreachUserInterest {
|
|
6
|
+
staleness: "fresh" | "stale" | "insufficient";
|
|
7
|
+
confidence: number;
|
|
8
|
+
signals: Array<{
|
|
9
|
+
topic: string;
|
|
10
|
+
confidence: number;
|
|
11
|
+
sourceRefs: ControlPlaneSourceRef[];
|
|
12
|
+
}>;
|
|
13
|
+
sourceRefs: ControlPlaneSourceRef[];
|
|
14
|
+
}
|
|
15
|
+
export interface JudgeOutreachLifeEvidence {
|
|
16
|
+
empty: boolean;
|
|
17
|
+
evidenceRefCount: number;
|
|
18
|
+
}
|
|
19
|
+
export interface JudgeOutreachInput {
|
|
20
|
+
candidate: CandidateIntent;
|
|
21
|
+
userInterest: JudgeOutreachUserInterest;
|
|
22
|
+
lifeEvidence: JudgeOutreachLifeEvidence;
|
|
23
|
+
delivery: DeliveryCapabilitySnapshot;
|
|
24
|
+
duplicateBlocked?: boolean;
|
|
25
|
+
cooldownBlocked?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface OutreachJudgment {
|
|
28
|
+
decisionId: string;
|
|
29
|
+
candidateId: string;
|
|
30
|
+
verdict: OutreachJudgmentVerdict;
|
|
31
|
+
valueScore: number;
|
|
32
|
+
userRelevance: number;
|
|
33
|
+
actionability: number;
|
|
34
|
+
interestRefs: ControlPlaneSourceRef[];
|
|
35
|
+
sourceRefs: ControlPlaneSourceRef[];
|
|
36
|
+
cooldownState: CooldownState;
|
|
37
|
+
deliveryVerdict: DeliveryTargetResolution["verdict"];
|
|
38
|
+
reasons: string[];
|
|
39
|
+
}
|
|
40
|
+
export declare function judgeOutreach(input: JudgeOutreachInput): OutreachJudgment;
|
|
@@ -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
|
+
}
|