@haaaiawd/second-nature 0.1.26 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/SKILL.md +35 -0
  2. package/agent-inner-guide.md +144 -0
  3. package/index.js +280 -2
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +4 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +3 -13
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/credential-vault.js +31 -17
  138. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  139. package/runtime/storage/services/diary-dream-store.js +165 -0
  140. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  141. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  142. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  143. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  144. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  145. package/runtime/storage/services/history-digest-store.js +140 -0
  146. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  147. package/runtime/storage/services/identity-profile-store.js +81 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  149. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  150. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  151. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  153. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  154. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  155. package/runtime/storage/services/tool-experience-store.js +116 -0
  156. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  157. package/runtime/storage/services/write-validation-gate.js +200 -0
  158. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,231 @@
1
+ import * as crypto from "node:crypto";
2
+ // ─── Configuration ──────────────────────────────────────────────────────────
3
+ const RETRY_DELAYS_MS = [500, 1000, 2000];
4
+ const REACTION_WEIGHTS = {
5
+ reply: 1.0,
6
+ react: 0.5,
7
+ ignore: -0.2,
8
+ block: -1.0,
9
+ };
10
+ const REDACTION_PATTERNS = [
11
+ /\b\d{3}-\d{3}-\d{4}\b/g, // phone
12
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // email
13
+ /\b(?:\d{4}[-\s]?){3}\d{4}\b/g, // credit card
14
+ ];
15
+ const MIN_TRUST_THRESHOLD = -0.5;
16
+ // ─── Helpers ────────────────────────────────────────────────────────────────
17
+ function validateFeedback(feedback) {
18
+ const errors = [];
19
+ if (!feedback.deliveryResult) {
20
+ errors.push("missing_delivery_result");
21
+ }
22
+ if (!feedback.ownerReaction) {
23
+ errors.push("missing_owner_reaction");
24
+ }
25
+ if (!feedback.timestamp) {
26
+ errors.push("missing_timestamp");
27
+ }
28
+ else {
29
+ const feedbackTime = new Date(feedback.timestamp);
30
+ if (Number.isNaN(feedbackTime.getTime())) {
31
+ errors.push("invalid_timestamp");
32
+ }
33
+ else {
34
+ const ageDays = (Date.now() - feedbackTime.getTime()) / (1000 * 60 * 60 * 24);
35
+ if (ageDays > 30) {
36
+ errors.push("feedback_too_old");
37
+ }
38
+ }
39
+ }
40
+ return { valid: errors.length === 0, errors };
41
+ }
42
+ function redactReactionContent(content) {
43
+ if (!content)
44
+ return content;
45
+ let redacted = content;
46
+ for (const pattern of REDACTION_PATTERNS) {
47
+ redacted = redacted.replace(pattern, "[REDACTED]");
48
+ }
49
+ return redacted;
50
+ }
51
+ function coerceDeliveryResult(feedback) {
52
+ if (feedback.deliveryResult === "sent") {
53
+ const hasProof = Boolean(feedback.deliveryProof?.messageId?.trim()) || Boolean(feedback.deliveryProof?.hostProofRef);
54
+ if (!hasProof) {
55
+ return "not_sent";
56
+ }
57
+ }
58
+ return feedback.deliveryResult;
59
+ }
60
+ function extractToneScore(content) {
61
+ if (!content)
62
+ return 0;
63
+ const positiveWords = ["thanks", "good", "great", "love", "awesome"];
64
+ const negativeWords = ["bad", "terrible", "hate", "wrong", "stop"];
65
+ const lower = content.toLowerCase();
66
+ const pos = positiveWords.filter((w) => lower.includes(w)).length;
67
+ const neg = negativeWords.filter((w) => lower.includes(w)).length;
68
+ if (pos > neg)
69
+ return 1.0;
70
+ if (neg > pos)
71
+ return -0.5;
72
+ return 0;
73
+ }
74
+ function extractTone(content) {
75
+ const score = extractToneScore(content);
76
+ if (score > 0.3)
77
+ return "positive";
78
+ if (score < -0.3)
79
+ return "negative";
80
+ return "neutral";
81
+ }
82
+ function calculateTiming(timestamp) {
83
+ const hours = (Date.now() - new Date(timestamp).getTime()) / (1000 * 60 * 60);
84
+ if (hours < 1)
85
+ return "immediate";
86
+ if (hours < 24)
87
+ return "delayed";
88
+ return "very_delayed";
89
+ }
90
+ function buildRelationshipUpdate(feedback) {
91
+ const redactedContent = redactReactionContent(feedback.reactionContent);
92
+ const toneScore = extractToneScore(redactedContent);
93
+ const reactionWeight = REACTION_WEIGHTS[feedback.ownerReaction];
94
+ const deliverySuccess = coerceDeliveryResult(feedback) === "sent";
95
+ return {
96
+ channelId: feedback.channelId,
97
+ timestamp: feedback.timestamp,
98
+ trustDelta: reactionWeight * (1 + toneScore * 0.5),
99
+ responsePattern: {
100
+ reaction: feedback.ownerReaction,
101
+ timing: calculateTiming(feedback.timestamp),
102
+ tone: extractTone(redactedContent),
103
+ },
104
+ deliverySuccess,
105
+ };
106
+ }
107
+ function applyTrustDecay(currentTrust, lastUpdated) {
108
+ if (!lastUpdated)
109
+ return currentTrust;
110
+ const daysSince = (Date.now() - new Date(lastUpdated).getTime()) / (1000 * 60 * 60 * 24);
111
+ const decayFactor = Math.pow(0.95, daysSince);
112
+ return currentTrust * decayFactor;
113
+ }
114
+ function updateResponsePatterns(existing, update, observedAt) {
115
+ const entry = {
116
+ reaction: update.reaction,
117
+ timing: update.timing,
118
+ tone: update.tone,
119
+ observedAt,
120
+ };
121
+ // Keep last 20 patterns
122
+ return [...existing, entry].slice(-20);
123
+ }
124
+ function updateChannelPreferences(existing, channelId, deliverySuccess) {
125
+ const map = new Map(existing.map((p) => [p.channelId, { ...p }]));
126
+ const pref = map.get(channelId) ?? { channelId, successRate: 0.5 };
127
+ // Exponential moving average of success rate
128
+ pref.successRate = pref.successRate * 0.7 + (deliverySuccess ? 1 : 0) * 0.3;
129
+ pref.lastUsedAt = new Date().toISOString();
130
+ map.set(channelId, pref);
131
+ return Array.from(map.values());
132
+ }
133
+ function generateStrategyAdjustments(update, memory) {
134
+ const adjustments = [];
135
+ if (memory.trustDelta < 0) {
136
+ adjustments.push({
137
+ type: "frequency",
138
+ adjustment: "decrease",
139
+ reason: "negative_trust_delta",
140
+ value: 0.5,
141
+ });
142
+ }
143
+ if (update.responsePattern.reaction === "block") {
144
+ adjustments.push({
145
+ type: "tone",
146
+ adjustment: "more_cautious",
147
+ reason: "user_blocked",
148
+ });
149
+ }
150
+ if (update.responsePattern.timing === "very_delayed") {
151
+ adjustments.push({
152
+ type: "timing",
153
+ adjustment: "increase_cooldown",
154
+ reason: "slow_response",
155
+ });
156
+ }
157
+ return adjustments;
158
+ }
159
+ async function sleep(ms) {
160
+ return new Promise((resolve) => setTimeout(resolve, ms));
161
+ }
162
+ async function persistWithRetry(memory, port) {
163
+ for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
164
+ try {
165
+ await port.updateRelationshipMemory(memory);
166
+ return { success: true, retryCount: attempt };
167
+ }
168
+ catch {
169
+ if (attempt < RETRY_DELAYS_MS.length) {
170
+ await sleep(RETRY_DELAYS_MS[attempt]);
171
+ }
172
+ }
173
+ }
174
+ return { success: false, retryCount: RETRY_DELAYS_MS.length };
175
+ }
176
+ function hashSummary(feedback) {
177
+ const data = `${feedback.channelId}:${feedback.deliveryResult}:${feedback.ownerReaction}:${feedback.timestamp}`;
178
+ return crypto.createHash("sha256").update(data).digest("hex");
179
+ }
180
+ // ─── Service ────────────────────────────────────────────────────────────────
181
+ export async function ingestChannelFeedback(feedback, deps) {
182
+ // Step 1: Validate
183
+ const validation = validateFeedback(feedback);
184
+ if (!validation.valid) {
185
+ return { status: "rejected", errors: validation.errors };
186
+ }
187
+ // Step 2: Build relationship update (with redaction + delivery-truth coercion)
188
+ const relationshipUpdate = buildRelationshipUpdate(feedback);
189
+ // Step 3: Load current memory
190
+ const currentMemory = await deps.relationshipPort.loadRelationshipMemory();
191
+ // Step 4: Apply trust decay
192
+ const decayedTrust = applyTrustDecay(currentMemory.trustDelta, currentMemory.lastUpdated);
193
+ // Step 5: Compute new trust
194
+ const newTrust = Math.max(MIN_TRUST_THRESHOLD, decayedTrust + relationshipUpdate.trustDelta);
195
+ // Step 6: Update patterns and preferences
196
+ const updatedPatterns = updateResponsePatterns(currentMemory.responsePatterns, relationshipUpdate.responsePattern, feedback.timestamp);
197
+ const updatedPreferences = updateChannelPreferences(currentMemory.channelPreferences, feedback.channelId, relationshipUpdate.deliverySuccess);
198
+ // Step 7: Assemble updated memory
199
+ const updatedMemory = {
200
+ ...currentMemory,
201
+ trustDelta: newTrust,
202
+ responsePatterns: updatedPatterns,
203
+ channelPreferences: updatedPreferences,
204
+ lastUpdated: new Date().toISOString(),
205
+ };
206
+ // Step 8: Persist with retry
207
+ const persistResult = await persistWithRetry(updatedMemory, deps.relationshipPort);
208
+ // Step 9: If all retries failed, audit and return failed_after_retries
209
+ if (!persistResult.success) {
210
+ await deps.auditPort.recordFeedbackIngestionFailed({
211
+ feedbackId: feedback.messageId ?? "unknown",
212
+ channelId: feedback.channelId,
213
+ summaryHash: hashSummary(feedback),
214
+ retryCount: persistResult.retryCount,
215
+ });
216
+ return {
217
+ status: "failed_after_retries",
218
+ relationshipUpdate,
219
+ strategyAdjustments: generateStrategyAdjustments(relationshipUpdate, updatedMemory),
220
+ updatedTrust: newTrust,
221
+ };
222
+ }
223
+ // Step 10: Generate strategy adjustments
224
+ const strategyAdjustments = generateStrategyAdjustments(relationshipUpdate, updatedMemory);
225
+ return {
226
+ status: "ingested",
227
+ relationshipUpdate,
228
+ strategyAdjustments,
229
+ updatedTrust: newTrust,
230
+ };
231
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * GuidanceDraftService — T-GVS.C.1
3
+ *
4
+ * Core logic: source-backed draft generation with delivery-time re-validation.
5
+ * Implements DR-028 (validateDraftSources) and DR-030 (GuidanceDraftRequest 7-field contract).
6
+ *
7
+ * Boundary:
8
+ * - Generates DraftMessage with source_refs traced to evidence pack claims.
9
+ * - validateDraftSources checks evidence availability before delivery; marks invalid
10
+ * if any source has been redacted or deleted, returning `draft_source_invalidated`.
11
+ * - Does not own delivery; caller must validate before sending.
12
+ *
13
+ * Test coverage: tests/unit/guidance/guidance-draft-service.test.ts
14
+ */
15
+ import type { GuidanceSourceRef } from "./outreach-draft-schema.js";
16
+ export interface GuidanceDraftRequest {
17
+ requestId: string;
18
+ sceneKind: "outreach" | "follow_up" | "reconnect";
19
+ evidencePackRef: string;
20
+ relationshipContextRef: string;
21
+ channelHint?: string;
22
+ ownerPreferenceRef?: string;
23
+ requestedAt: string;
24
+ }
25
+ export interface DraftMessage {
26
+ text: string;
27
+ deliveryWording: "sendable" | "not_sent_fallback_candidate";
28
+ sourceRefs: GuidanceSourceRef[];
29
+ explanation?: string;
30
+ }
31
+ export type DraftServiceError = "draft_source_invalidated" | "evidence_pack_unavailable" | "unsupported_scene_kind";
32
+ export interface DraftServiceResult {
33
+ draft?: DraftMessage;
34
+ error?: DraftServiceError;
35
+ }
36
+ export interface DraftEvidencePackClaim {
37
+ id: string;
38
+ text: string;
39
+ sourceRefs: GuidanceSourceRef[];
40
+ }
41
+ export interface DraftEvidencePack {
42
+ claims: DraftEvidencePackClaim[];
43
+ }
44
+ export interface DraftEvidencePackPort {
45
+ loadEvidencePack(ref: string): Promise<DraftEvidencePack | undefined>;
46
+ }
47
+ export interface SourceValidatorPort {
48
+ checkSourceAvailable(sourceRef: GuidanceSourceRef): Promise<boolean>;
49
+ }
50
+ export declare function generateGuidanceDraft(request: GuidanceDraftRequest, deps: {
51
+ evidencePort: DraftEvidencePackPort;
52
+ }): Promise<DraftServiceResult>;
53
+ export interface DraftValidationResult {
54
+ valid: boolean;
55
+ invalidated?: boolean;
56
+ reason?: DraftServiceError;
57
+ }
58
+ export declare function validateDraftSources(draft: DraftMessage, deps: {
59
+ validatorPort: SourceValidatorPort;
60
+ }): Promise<DraftValidationResult>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * GuidanceDraftService — T-GVS.C.1
3
+ *
4
+ * Core logic: source-backed draft generation with delivery-time re-validation.
5
+ * Implements DR-028 (validateDraftSources) and DR-030 (GuidanceDraftRequest 7-field contract).
6
+ *
7
+ * Boundary:
8
+ * - Generates DraftMessage with source_refs traced to evidence pack claims.
9
+ * - validateDraftSources checks evidence availability before delivery; marks invalid
10
+ * if any source has been redacted or deleted, returning `draft_source_invalidated`.
11
+ * - Does not own delivery; caller must validate before sending.
12
+ *
13
+ * Test coverage: tests/unit/guidance/guidance-draft-service.test.ts
14
+ */
15
+ // ─── Service ────────────────────────────────────────────────────────────────
16
+ const SCENE_KINDS = [
17
+ "outreach",
18
+ "follow_up",
19
+ "reconnect",
20
+ ];
21
+ function buildDraftText(request, claims) {
22
+ const anchor = claims.map((c) => c.text).join("; ");
23
+ switch (request.sceneKind) {
24
+ case "outreach":
25
+ return `Hi there — wanted to share something that came up: ${anchor}`;
26
+ case "follow_up":
27
+ return `Following up on what we talked about: ${anchor}`;
28
+ case "reconnect":
29
+ return `It's been a while — here's what caught my attention: ${anchor}`;
30
+ default:
31
+ return `Draft for ${request.sceneKind}: ${anchor}`;
32
+ }
33
+ }
34
+ export async function generateGuidanceDraft(request, deps) {
35
+ if (!SCENE_KINDS.includes(request.sceneKind)) {
36
+ return { error: "unsupported_scene_kind" };
37
+ }
38
+ const pack = await deps.evidencePort.loadEvidencePack(request.evidencePackRef);
39
+ if (!pack) {
40
+ return { error: "evidence_pack_unavailable" };
41
+ }
42
+ const allSourceRefs = pack.claims.flatMap((c) => c.sourceRefs);
43
+ if (allSourceRefs.length === 0) {
44
+ return { error: "draft_source_invalidated" };
45
+ }
46
+ const text = buildDraftText(request, pack.claims);
47
+ const parts = [
48
+ `evidencePack=${request.evidencePackRef}`,
49
+ `relationshipContext=${request.relationshipContextRef}`,
50
+ ];
51
+ if (request.channelHint) {
52
+ parts.push(`channel=${request.channelHint}`);
53
+ }
54
+ if (request.ownerPreferenceRef) {
55
+ parts.push(`ownerPreference=${request.ownerPreferenceRef}`);
56
+ }
57
+ return {
58
+ draft: {
59
+ text,
60
+ deliveryWording: "sendable",
61
+ sourceRefs: allSourceRefs,
62
+ explanation: parts.join("; "),
63
+ },
64
+ };
65
+ }
66
+ export async function validateDraftSources(draft, deps) {
67
+ const results = await Promise.all(draft.sourceRefs.map(async (ref) => ({
68
+ ref,
69
+ available: await deps.validatorPort.checkSourceAvailable(ref),
70
+ })));
71
+ const firstInvalid = results.find((r) => !r.available);
72
+ if (firstInvalid) {
73
+ return {
74
+ valid: false,
75
+ invalidated: true,
76
+ reason: "draft_source_invalidated",
77
+ };
78
+ }
79
+ return { valid: true };
80
+ }
@@ -8,5 +8,8 @@ export * from "./review-workflow.js";
8
8
  export * from "./guidance-assembler.js";
9
9
  export * from "./outreach-draft-schema.js";
10
10
  export * from "./draft-outreach-message.js";
11
+ export * from "./guidance-draft-service.js";
12
+ export * from "./channel-feedback-ingestion-service.js";
13
+ export * from "./outreach-strategy-selector.js";
11
14
  export * from "./evidence-guidance.js";
12
15
  export * from "./draft-narrative-outreach.js";
@@ -8,5 +8,8 @@ export * from "./review-workflow.js";
8
8
  export * from "./guidance-assembler.js";
9
9
  export * from "./outreach-draft-schema.js";
10
10
  export * from "./draft-outreach-message.js";
11
+ export * from "./guidance-draft-service.js";
12
+ export * from "./channel-feedback-ingestion-service.js";
13
+ export * from "./outreach-strategy-selector.js";
11
14
  export * from "./evidence-guidance.js";
12
15
  export * from "./draft-narrative-outreach.js";
@@ -52,15 +52,15 @@ export declare const sceneGuidanceRequestSchema: z.ZodObject<{
52
52
  }>;
53
53
  rhythmWindowKind: z.ZodOptional<z.ZodEnum<{
54
54
  quiet: "quiet";
55
- social: "social";
56
55
  maintenance: "maintenance";
57
- work: "work";
58
56
  exploration: "exploration";
57
+ social: "social";
58
+ work: "work";
59
59
  reflection: "reflection";
60
60
  }>>;
61
61
  riskLevel: z.ZodEnum<{
62
- medium: "medium";
63
62
  low: "low";
63
+ medium: "medium";
64
64
  high: "high";
65
65
  }>;
66
66
  sourceRefs: z.ZodArray<z.ZodObject<{
@@ -146,15 +146,15 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
146
146
  }>;
147
147
  rhythmWindowKind: z.ZodOptional<z.ZodEnum<{
148
148
  quiet: "quiet";
149
- social: "social";
150
149
  maintenance: "maintenance";
151
- work: "work";
152
150
  exploration: "exploration";
151
+ social: "social";
152
+ work: "work";
153
153
  reflection: "reflection";
154
154
  }>>;
155
155
  riskLevel: z.ZodEnum<{
156
- medium: "medium";
157
156
  low: "low";
157
+ medium: "medium";
158
158
  high: "high";
159
159
  }>;
160
160
  sourceRefs: z.ZodArray<z.ZodObject<{
@@ -265,7 +265,7 @@ export declare function parseOutreachDraftRequest(input: unknown): OutreachDraft
265
265
  export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafeParseResult<{
266
266
  requestId: string;
267
267
  runtimeScope: "rhythm" | "user_reply" | "user_task";
268
- riskLevel: "medium" | "low" | "high";
268
+ riskLevel: "low" | "medium" | "high";
269
269
  sourceRefs: {
270
270
  id: string;
271
271
  kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
@@ -285,7 +285,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
285
285
  excerptHash?: string | undefined;
286
286
  observedAt?: string | undefined;
287
287
  }[];
288
- rhythmWindowKind?: "quiet" | "social" | "maintenance" | "work" | "exploration" | "reflection" | undefined;
288
+ rhythmWindowKind?: "quiet" | "maintenance" | "exploration" | "social" | "work" | "reflection" | undefined;
289
289
  deliveryContext?: {
290
290
  deliveryVerdict: "target_none" | "channel_missing" | "host_unsupported" | "delivery_failed" | "target_available";
291
291
  wordingMode: "sendable" | "not_sent_fallback_candidate";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * OutreachStrategySelector — T-GVS.C.3
3
+ *
4
+ * Core logic: select expression frequency, phrasing style and fallback copy
5
+ * based on RelationshipMemory; apply DR-031 language quality lint (3 rules).
6
+ * style_lint_failed is a degraded marker — it never blocks delivery.
7
+ * fallback copy always has information value (not empty string).
8
+ *
9
+ * DR-031 lint rules:
10
+ * 1. no_dry_filler — no hollow phrases ("reach out", "touch base", "just checking in", etc.)
11
+ * 2. anchored — must contain a concrete source anchor (specific fact/observation reference)
12
+ * 3. no_over_explain — no excessive qualifications (≥3 hedge phrases in one draft)
13
+ *
14
+ * Boundary:
15
+ * - Reads RelationshipMemory (channelPreferences, responsePatterns, trustDelta).
16
+ * - Does NOT write state; returns OutreachStrategy recommendation only.
17
+ * - style_lint checks run on draft text passed in; strategy selection itself is rule-based.
18
+ *
19
+ * Test coverage:
20
+ * tests/unit/guidance/outreach-strategy-selector.test.ts
21
+ * tests/unit/guidance/outreach-style-fixtures.test.ts
22
+ */
23
+ import type { RelationshipMemory } from "./channel-feedback-ingestion-service.js";
24
+ export type OutreachFrequency = "standard" | "reduced" | "minimal" | "paused";
25
+ export type OutreachStyle = "warm_anchored" | "concise_factual" | "light_check";
26
+ export interface OutreachStrategy {
27
+ frequency: OutreachFrequency;
28
+ style: OutreachStyle;
29
+ fallbackCopy: FallbackCopy;
30
+ rationale: string;
31
+ }
32
+ export interface FallbackCopy {
33
+ text: string;
34
+ hasInformationValue: boolean;
35
+ sourceAnchor?: string;
36
+ channelSafeReason: string;
37
+ }
38
+ export interface StyleLintResult {
39
+ passed: boolean;
40
+ violations: StyleLintViolation[];
41
+ /**
42
+ * If lint fails, this is the degraded marker (DR-031).
43
+ * Never blocks delivery — caller decides what to do with it.
44
+ */
45
+ lintStatus: "passed" | "style_lint_failed";
46
+ hitRules: StyleLintRule[];
47
+ }
48
+ export type StyleLintRule = "no_dry_filler" | "anchored" | "no_over_explain";
49
+ export interface StyleLintViolation {
50
+ rule: StyleLintRule;
51
+ description: string;
52
+ }
53
+ export interface FallbackContext {
54
+ sourceRefs: string[];
55
+ reason: string;
56
+ channelId?: string;
57
+ ownerPreferenceRef?: string;
58
+ }
59
+ /**
60
+ * Run DR-031 language quality checklist on a draft text.
61
+ * Returns StyleLintResult. style_lint_failed is a degraded marker — never blocks delivery.
62
+ */
63
+ export declare function runStyleLint(draftText: string): StyleLintResult;
64
+ /**
65
+ * Build channel-safe fallback copy that always has information value (DR-031 G4).
66
+ * Never returns empty string. Includes sourceRefs anchor and human-readable channel reason.
67
+ */
68
+ export declare function buildFallbackCopy(ctx: FallbackContext): FallbackCopy;
69
+ export interface OutreachStrategySelectorOptions {
70
+ fallbackContext?: FallbackContext;
71
+ }
72
+ /**
73
+ * Select outreach strategy based on RelationshipMemory.
74
+ * Returns OutreachStrategy (frequency + style + fallbackCopy + rationale).
75
+ * Does NOT write state.
76
+ */
77
+ export declare function selectOutreachStrategy(memory: RelationshipMemory, options?: OutreachStrategySelectorOptions): OutreachStrategy;