@haaaiawd/second-nature 0.1.27 → 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.
- package/SKILL.md +35 -33
- package/agent-inner-guide.md +144 -124
- package/index.js +76 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -1
- package/runtime/cli/commands/connector-behavior.d.ts +20 -0
- package/runtime/cli/commands/connector-behavior.js +160 -0
- package/runtime/cli/commands/index.js +8 -0
- package/runtime/cli/index.js +9 -2
- package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
- package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
- package/runtime/cli/ops/ops-router.d.ts +45 -4
- package/runtime/cli/ops/ops-router.js +543 -2
- package/runtime/cli/read-models/index.js +35 -18
- package/runtime/cli/read-models/types.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
- package/runtime/connectors/base/contract.d.ts +4 -1
- package/runtime/connectors/base/contract.js +5 -1
- package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
- package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
- package/runtime/connectors/base/failure-taxonomy.js +5 -0
- package/runtime/connectors/base/manifest-v7.d.ts +151 -0
- package/runtime/connectors/base/manifest-v7.js +170 -0
- package/runtime/connectors/base/manifest.d.ts +67 -77
- package/runtime/connectors/base/manifest.js +7 -7
- package/runtime/connectors/base/route-planner.js +11 -8
- package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
- package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
- package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
- package/runtime/connectors/base/wet-probe-runner.js +132 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
- package/runtime/connectors/manifest/manifest-schema.js +2 -0
- package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
- package/runtime/connectors/services/connector-executor-adapter.js +132 -26
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
- package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
- package/runtime/core/second-nature/heartbeat/index.js +5 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
- package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
- package/runtime/core/second-nature/orchestrator/index.js +7 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
- package/runtime/core/second-nature/quiet/index.d.ts +5 -0
- package/runtime/core/second-nature/quiet/index.js +5 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
- package/runtime/core/second-nature/types.d.ts +2 -0
- package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
- package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
- package/runtime/guidance/guidance-draft-service.d.ts +60 -0
- package/runtime/guidance/guidance-draft-service.js +80 -0
- package/runtime/guidance/index.d.ts +3 -0
- package/runtime/guidance/index.js +3 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
- package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
- package/runtime/guidance/outreach-strategy-selector.js +211 -0
- package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
- package/runtime/observability/audit/append-only-audit-store.js +32 -6
- package/runtime/observability/audit/audit-envelope.d.ts +2 -1
- package/runtime/observability/audit/audit-envelope.js +8 -7
- package/runtime/observability/audit/audit-family-registry.json +66 -0
- package/runtime/observability/audit/family-registry.d.ts +43 -0
- package/runtime/observability/audit/family-registry.js +70 -0
- package/runtime/observability/index.d.ts +6 -1
- package/runtime/observability/index.js +6 -1
- package/runtime/observability/redaction/policy.d.ts +24 -3
- package/runtime/observability/redaction/policy.js +74 -0
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
- package/runtime/observability/services/lived-experience-audit.js +6 -6
- package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
- package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
- package/runtime/observability/services/restore-audit-service.d.ts +74 -0
- package/runtime/observability/services/restore-audit-service.js +79 -0
- package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
- package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
- package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
- package/runtime/observability/services/self-health-snapshot.js +251 -0
- package/runtime/shared/types/goal.d.ts +62 -0
- package/runtime/shared/types/goal.js +20 -0
- package/runtime/shared/types/index.d.ts +3 -0
- package/runtime/shared/types/index.js +3 -0
- package/runtime/shared/types/source-ref.d.ts +14 -0
- package/runtime/shared/types/source-ref.js +1 -0
- package/runtime/shared/types/v7-entities.d.ts +206 -0
- package/runtime/shared/types/v7-entities.js +27 -0
- package/runtime/storage/db/index.js +3 -0
- package/runtime/storage/db/migration-runner.d.ts +30 -0
- package/runtime/storage/db/migration-runner.js +93 -0
- package/runtime/storage/db/migrations/index.d.ts +5 -0
- package/runtime/storage/db/migrations/index.js +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
- package/runtime/storage/db/schema/agent-goal.js +2 -0
- package/runtime/storage/db/transaction-utils.d.ts +14 -0
- package/runtime/storage/db/transaction-utils.js +29 -0
- package/runtime/storage/db/write-queue.d.ts +38 -0
- package/runtime/storage/db/write-queue.js +97 -0
- package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
- package/runtime/storage/services/diary-dream-store.d.ts +35 -0
- package/runtime/storage/services/diary-dream-store.js +165 -0
- package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
- package/runtime/storage/services/embodied-context-state-port.js +115 -0
- package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
- package/runtime/storage/services/goal-lifecycle-store.js +181 -0
- package/runtime/storage/services/history-digest-store.d.ts +33 -0
- package/runtime/storage/services/history-digest-store.js +140 -0
- package/runtime/storage/services/identity-profile-store.d.ts +25 -0
- package/runtime/storage/services/identity-profile-store.js +81 -0
- package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
- package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
- package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
- package/runtime/storage/services/restore-snapshot-store.js +193 -0
- package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
- package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
- package/runtime/storage/services/tool-experience-store.d.ts +25 -0
- package/runtime/storage/services/tool-experience-store.js +116 -0
- package/runtime/storage/services/write-validation-gate.d.ts +46 -0
- package/runtime/storage/services/write-validation-gate.js +200 -0
- 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: "
|
|
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" | "
|
|
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;
|