@haaaiawd/second-nature 0.1.38 → 0.1.40
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/agent-inner-guide.md +18 -0
- package/index.js +10 -2
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/runtime/cli/commands/connector-init.js +11 -4
- package/runtime/cli/index.js +6 -1
- package/runtime/cli/ops/heartbeat-surface.d.ts +15 -0
- package/runtime/cli/ops/heartbeat-surface.js +16 -2
- package/runtime/cli/ops/ops-router.js +229 -83
- package/runtime/cli/ops/workspace-heartbeat-runner.js +49 -4
- package/runtime/connectors/services/connector-executor-adapter.js +192 -41
- package/runtime/core/second-nature/guidance/apply-guidance.d.ts +2 -0
- package/runtime/core/second-nature/guidance/apply-guidance.js +6 -1
- package/runtime/core/second-nature/guidance/user-reply-continuity.d.ts +1 -1
- package/runtime/core/second-nature/guidance/user-reply-continuity.js +14 -5
- package/runtime/core/second-nature/orchestrator/intent-planner.js +15 -0
- package/runtime/core/second-nature/runtime/service-entry.d.ts +3 -0
- package/runtime/core/second-nature/runtime/service-entry.js +1 -2
- package/runtime/dream/dream-engine.d.ts +14 -0
- package/runtime/dream/dream-engine.js +306 -0
- package/runtime/dream/dream-input-loader.d.ts +37 -0
- package/runtime/dream/dream-input-loader.js +155 -0
- package/runtime/dream/dream-scheduler.d.ts +75 -0
- package/runtime/dream/dream-scheduler.js +131 -0
- package/runtime/dream/index.d.ts +16 -0
- package/runtime/dream/index.js +14 -0
- package/runtime/dream/insight-extractor.d.ts +32 -0
- package/runtime/dream/insight-extractor.js +135 -0
- package/runtime/dream/memory-consolidator.d.ts +45 -0
- package/runtime/dream/memory-consolidator.js +140 -0
- package/runtime/dream/narrative-update-proposal.d.ts +34 -0
- package/runtime/dream/narrative-update-proposal.js +83 -0
- package/runtime/dream/output-validator.d.ts +20 -0
- package/runtime/dream/output-validator.js +110 -0
- package/runtime/dream/redaction-gate.d.ts +31 -0
- package/runtime/dream/redaction-gate.js +109 -0
- package/runtime/dream/relationship-update-proposal.d.ts +27 -0
- package/runtime/dream/relationship-update-proposal.js +119 -0
- package/runtime/dream/sampler.d.ts +30 -0
- package/runtime/dream/sampler.js +65 -0
- package/runtime/dream/types.d.ts +187 -0
- package/runtime/dream/types.js +11 -0
- package/runtime/guidance/fallback.js +6 -3
- package/runtime/guidance/guidance-assembler.js +5 -3
- package/runtime/guidance/output-guard.d.ts +4 -1
- package/runtime/guidance/output-guard.js +24 -0
- package/runtime/guidance/template-registry.d.ts +5 -1
- package/runtime/guidance/template-registry.js +71 -30
- package/runtime/guidance/types.d.ts +14 -0
- package/runtime/observability/projections/guidance-audit.js +4 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream output validator.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: schema, source grounding, sensitivity, and unsupported claim
|
|
5
|
+
* checks on candidate DreamOutput. Decides accepted eligibility or archive reason.
|
|
6
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
7
|
+
*/
|
|
8
|
+
function hasUnsupportedClaims(output) {
|
|
9
|
+
const claims = [];
|
|
10
|
+
for (const insight of output.insights) {
|
|
11
|
+
if (insight.confidence < 0.3) {
|
|
12
|
+
claims.push(`insight_low_confidence:${insight.id}`);
|
|
13
|
+
}
|
|
14
|
+
if (insight.sourceRefs.length === 0) {
|
|
15
|
+
claims.push(`insight_no_source:${insight.id}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (output.narrativeUpdate) {
|
|
19
|
+
if (output.narrativeUpdate.unsupportedClaims.length > 0) {
|
|
20
|
+
claims.push(...output.narrativeUpdate.unsupportedClaims);
|
|
21
|
+
}
|
|
22
|
+
if (output.narrativeUpdate.sourceRefs.length === 0) {
|
|
23
|
+
claims.push("narrative_update_no_source");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (output.relationshipUpdate) {
|
|
27
|
+
if (output.relationshipUpdate.sourceRefs.length === 0) {
|
|
28
|
+
claims.push("relationship_update_no_source");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return claims;
|
|
32
|
+
}
|
|
33
|
+
function isSourceGrounded(output, inputEvidenceIds, inputChronicleIds, inputToolExperienceIds) {
|
|
34
|
+
const validSourceIds = new Set([
|
|
35
|
+
...inputEvidenceIds,
|
|
36
|
+
...inputChronicleIds,
|
|
37
|
+
...(inputToolExperienceIds ?? []),
|
|
38
|
+
]);
|
|
39
|
+
for (const entry of output.canonicalEntries) {
|
|
40
|
+
for (const ref of entry.sourceRefs) {
|
|
41
|
+
if (!validSourceIds.has(ref.sourceId)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const insight of output.insights) {
|
|
47
|
+
for (const refId of insight.sourceRefs) {
|
|
48
|
+
if (!validSourceIds.has(refId)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function hasSensitivityIssues(output) {
|
|
56
|
+
const issues = [];
|
|
57
|
+
for (const entry of output.canonicalEntries) {
|
|
58
|
+
const text = JSON.stringify(entry).toLowerCase();
|
|
59
|
+
if (text.includes("password") || text.includes("token") || text.includes("secret")) {
|
|
60
|
+
issues.push(`sensitivity_in_entry:${entry.entryId}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return issues;
|
|
64
|
+
}
|
|
65
|
+
export function validateDreamOutput(input) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
const archiveReasons = [];
|
|
68
|
+
// Schema: basic structural checks
|
|
69
|
+
const schemaValid = typeof input.output.outputId === "string" &&
|
|
70
|
+
input.output.outputId.length > 0 &&
|
|
71
|
+
typeof input.output.runId === "string" &&
|
|
72
|
+
Array.isArray(input.output.canonicalEntries) &&
|
|
73
|
+
Array.isArray(input.output.insights);
|
|
74
|
+
if (!schemaValid) {
|
|
75
|
+
errors.push("schema_invalid");
|
|
76
|
+
archiveReasons.push("schema_invalid");
|
|
77
|
+
}
|
|
78
|
+
// Source grounding
|
|
79
|
+
const sourceGrounded = isSourceGrounded(input.output, input.inputEvidenceIds, input.inputChronicleIds, input.inputToolExperienceIds);
|
|
80
|
+
if (!sourceGrounded) {
|
|
81
|
+
errors.push("source_not_grounded");
|
|
82
|
+
archiveReasons.push("source_not_grounded");
|
|
83
|
+
}
|
|
84
|
+
// Sensitivity
|
|
85
|
+
const sensitivityIssues = hasSensitivityIssues(input.output);
|
|
86
|
+
const sensitivityClean = sensitivityIssues.length === 0;
|
|
87
|
+
if (!sensitivityClean) {
|
|
88
|
+
errors.push(...sensitivityIssues);
|
|
89
|
+
archiveReasons.push(...sensitivityIssues);
|
|
90
|
+
}
|
|
91
|
+
// Unsupported claims
|
|
92
|
+
const unsupportedClaims = hasUnsupportedClaims(input.output);
|
|
93
|
+
if (unsupportedClaims.length > 0) {
|
|
94
|
+
errors.push(...unsupportedClaims);
|
|
95
|
+
archiveReasons.push("unsupported_claims_present");
|
|
96
|
+
}
|
|
97
|
+
const eligible = schemaValid && sourceGrounded && sensitivityClean && unsupportedClaims.length === 0;
|
|
98
|
+
return {
|
|
99
|
+
eligible,
|
|
100
|
+
validation: {
|
|
101
|
+
schemaValid,
|
|
102
|
+
sourceGrounded,
|
|
103
|
+
sensitivityClean,
|
|
104
|
+
unsupportedClaims,
|
|
105
|
+
errors,
|
|
106
|
+
checkedAt: new Date().toISOString(),
|
|
107
|
+
},
|
|
108
|
+
archiveReasons,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream redaction gate.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: before sending evidence to LLM, strip credential-like fields,
|
|
5
|
+
* PII patterns, and sensitive platform payload. If redaction fails or
|
|
6
|
+
* sensitivity is too high, block the LLM stage and record reason.
|
|
7
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
8
|
+
*/
|
|
9
|
+
import type { RedactedEvidenceBundle } from "./types.js";
|
|
10
|
+
export interface RedactionInput {
|
|
11
|
+
evidenceSummaries: string[];
|
|
12
|
+
chronicleSummaries: string[];
|
|
13
|
+
activeMemorySummaries?: string[];
|
|
14
|
+
sensitivityFlags?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface RedactionResult {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
redactedEvidence: string[];
|
|
19
|
+
redactedChronicle: string[];
|
|
20
|
+
redactedMemory: string[];
|
|
21
|
+
blockedReason?: string;
|
|
22
|
+
credentialHits: number;
|
|
23
|
+
piiHits: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Produce a RedactedEvidenceBundle brand type (DR-027).
|
|
27
|
+
* Must be called before passing evidence to ModelAssistPort.
|
|
28
|
+
* Returns null if redaction gate blocks the bundle.
|
|
29
|
+
*/
|
|
30
|
+
export declare function redactBundle(evidence: string[], chronicle: string[], memory?: string[]): RedactedEvidenceBundle | null;
|
|
31
|
+
export declare function redactDreamInput(input: RedactionInput): RedactionResult;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream redaction gate.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: before sending evidence to LLM, strip credential-like fields,
|
|
5
|
+
* PII patterns, and sensitive platform payload. If redaction fails or
|
|
6
|
+
* sensitivity is too high, block the LLM stage and record reason.
|
|
7
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
8
|
+
*/
|
|
9
|
+
const CREDENTIAL_PATTERNS = [
|
|
10
|
+
/password\s*[:=]\s*\S+/gi,
|
|
11
|
+
/token\s*[:=]\s*\S+/gi,
|
|
12
|
+
/api[_-]?key\s*[:=]\s*\S+/gi,
|
|
13
|
+
/secret\s*[:=]\s*\S+/gi,
|
|
14
|
+
/cookie\s*[:=]\s*\S+/gi,
|
|
15
|
+
/bearer\s+\S+/gi,
|
|
16
|
+
];
|
|
17
|
+
const PII_PATTERNS = [
|
|
18
|
+
/\b\d{3}-\d{2}-\d{4}\b/g, // SSN-like
|
|
19
|
+
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // Credit card-like
|
|
20
|
+
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, // Email
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Produce a RedactedEvidenceBundle brand type (DR-027).
|
|
24
|
+
* Must be called before passing evidence to ModelAssistPort.
|
|
25
|
+
* Returns null if redaction gate blocks the bundle.
|
|
26
|
+
*/
|
|
27
|
+
export function redactBundle(evidence, chronicle, memory) {
|
|
28
|
+
const result = redactDreamInput({
|
|
29
|
+
evidenceSummaries: evidence,
|
|
30
|
+
chronicleSummaries: chronicle,
|
|
31
|
+
activeMemorySummaries: memory ?? [],
|
|
32
|
+
});
|
|
33
|
+
if (!result.allowed)
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
_brand: "redacted",
|
|
37
|
+
evidence: Object.freeze(result.redactedEvidence),
|
|
38
|
+
chronicle: Object.freeze(result.redactedChronicle),
|
|
39
|
+
memory: result.redactedMemory.length > 0
|
|
40
|
+
? Object.freeze(result.redactedMemory)
|
|
41
|
+
: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function redactText(text) {
|
|
45
|
+
let redacted = text;
|
|
46
|
+
let credentialHits = 0;
|
|
47
|
+
let piiHits = 0;
|
|
48
|
+
for (const pattern of CREDENTIAL_PATTERNS) {
|
|
49
|
+
const matches = redacted.match(pattern);
|
|
50
|
+
if (matches) {
|
|
51
|
+
credentialHits += matches.length;
|
|
52
|
+
redacted = redacted.replace(pattern, "[REDACTED_CREDENTIAL]");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const pattern of PII_PATTERNS) {
|
|
56
|
+
const matches = redacted.match(pattern);
|
|
57
|
+
if (matches) {
|
|
58
|
+
piiHits += matches.length;
|
|
59
|
+
redacted = redacted.replace(pattern, "[REDACTED_PII]");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { redacted, credentialHits, piiHits };
|
|
63
|
+
}
|
|
64
|
+
export function redactDreamInput(input) {
|
|
65
|
+
let totalCredentialHits = 0;
|
|
66
|
+
let totalPiiHits = 0;
|
|
67
|
+
const redactList = (items) => items.map((item) => {
|
|
68
|
+
const result = redactText(item);
|
|
69
|
+
totalCredentialHits += result.credentialHits;
|
|
70
|
+
totalPiiHits += result.piiHits;
|
|
71
|
+
return result.redacted;
|
|
72
|
+
});
|
|
73
|
+
const redactedEvidence = redactList(input.evidenceSummaries);
|
|
74
|
+
const redactedChronicle = redactList(input.chronicleSummaries);
|
|
75
|
+
const redactedMemory = redactList(input.activeMemorySummaries ?? []);
|
|
76
|
+
// If any sensitivity flag is "credential" or "sensitive", block LLM stage
|
|
77
|
+
const hasHighSensitivity = (input.sensitivityFlags ?? []).some((f) => f === "credential" || f === "sensitive");
|
|
78
|
+
if (hasHighSensitivity) {
|
|
79
|
+
return {
|
|
80
|
+
allowed: false,
|
|
81
|
+
redactedEvidence,
|
|
82
|
+
redactedChronicle,
|
|
83
|
+
redactedMemory,
|
|
84
|
+
blockedReason: "sensitivity_flag_blocks_llm",
|
|
85
|
+
credentialHits: totalCredentialHits,
|
|
86
|
+
piiHits: totalPiiHits,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// If credential hits are excessive, also block
|
|
90
|
+
if (totalCredentialHits > 3) {
|
|
91
|
+
return {
|
|
92
|
+
allowed: false,
|
|
93
|
+
redactedEvidence,
|
|
94
|
+
redactedChronicle,
|
|
95
|
+
redactedMemory,
|
|
96
|
+
blockedReason: "excessive_credential_exposure",
|
|
97
|
+
credentialHits: totalCredentialHits,
|
|
98
|
+
piiHits: totalPiiHits,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
allowed: true,
|
|
103
|
+
redactedEvidence,
|
|
104
|
+
redactedChronicle,
|
|
105
|
+
redactedMemory,
|
|
106
|
+
credentialHits: totalCredentialHits,
|
|
107
|
+
piiHits: totalPiiHits,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship Update Proposal
|
|
3
|
+
*
|
|
4
|
+
* Core logic: generate relationship update proposal based on chronicle entries.
|
|
5
|
+
* Tone/timing/topic deltas include sourceRefs and confidence.
|
|
6
|
+
* Owner no-reply signal is recorded as cooldown without inventing preference.
|
|
7
|
+
* Prevents over-inference from single samples.
|
|
8
|
+
* Test coverage: tests/unit/dream/t7-1-5-relationship-update.test.ts
|
|
9
|
+
*/
|
|
10
|
+
import type { DreamRelationshipUpdate } from "./types.js";
|
|
11
|
+
export interface RelationshipProposalInput {
|
|
12
|
+
chronicleEntries: Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
kind?: string;
|
|
17
|
+
}>;
|
|
18
|
+
priorTone?: string;
|
|
19
|
+
priorTiming?: string;
|
|
20
|
+
priorTopic?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RelationshipProposalResult {
|
|
23
|
+
proposal?: DreamRelationshipUpdate;
|
|
24
|
+
unsupportedClaims: string[];
|
|
25
|
+
cooldown?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function draftRelationshipFromDream(input: RelationshipProposalInput): RelationshipProposalResult;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship Update Proposal
|
|
3
|
+
*
|
|
4
|
+
* Core logic: generate relationship update proposal based on chronicle entries.
|
|
5
|
+
* Tone/timing/topic deltas include sourceRefs and confidence.
|
|
6
|
+
* Owner no-reply signal is recorded as cooldown without inventing preference.
|
|
7
|
+
* Prevents over-inference from single samples.
|
|
8
|
+
* Test coverage: tests/unit/dream/t7-1-5-relationship-update.test.ts
|
|
9
|
+
*/
|
|
10
|
+
// Keywords for tone inference
|
|
11
|
+
const POSITIVE_TONE = [
|
|
12
|
+
"agree", "thanks", "appreciate", "helpful", "good", "great",
|
|
13
|
+
"love", "like", "enjoy", "excited", "happy",
|
|
14
|
+
];
|
|
15
|
+
const NEGATIVE_TONE = [
|
|
16
|
+
"disagree", "frustrated", "annoying", "bad", "hate", "dislike",
|
|
17
|
+
"angry", "upset", "disappointed", "concerned",
|
|
18
|
+
];
|
|
19
|
+
const BUSY_TIMING = [
|
|
20
|
+
"busy", "swamped", "occupied", "tight schedule", "no time",
|
|
21
|
+
];
|
|
22
|
+
// Keywords for topic inference
|
|
23
|
+
const TOPIC_PATTERNS = {
|
|
24
|
+
work: ["work", "project", "task", "job", "delivery", "deadline"],
|
|
25
|
+
personal: ["family", "life", "health", "weekend", "trip"],
|
|
26
|
+
tech: ["code", "system", "bug", "feature", "architecture", "design"],
|
|
27
|
+
social: ["friend", "community", "meetup", "event", "collaboration"],
|
|
28
|
+
};
|
|
29
|
+
function inferTone(text) {
|
|
30
|
+
const lower = text.toLowerCase();
|
|
31
|
+
const pos = POSITIVE_TONE.filter((w) => lower.includes(w)).length;
|
|
32
|
+
const neg = NEGATIVE_TONE.filter((w) => lower.includes(w)).length;
|
|
33
|
+
if (pos > neg && pos > 0)
|
|
34
|
+
return { tone: "positive", score: pos };
|
|
35
|
+
if (neg > pos && neg > 0)
|
|
36
|
+
return { tone: "negative", score: neg };
|
|
37
|
+
if (pos === neg && pos > 0)
|
|
38
|
+
return { tone: "mixed", score: pos };
|
|
39
|
+
return { tone: "neutral", score: 0 };
|
|
40
|
+
}
|
|
41
|
+
function inferTiming(text) {
|
|
42
|
+
const lower = text.toLowerCase();
|
|
43
|
+
const busy = BUSY_TIMING.filter((w) => lower.includes(w)).length;
|
|
44
|
+
if (busy > 0)
|
|
45
|
+
return { timing: "busy", score: busy };
|
|
46
|
+
// Check for quick replies (indicator in summary)
|
|
47
|
+
if (lower.includes("quick reply") || lower.includes("prompt response")) {
|
|
48
|
+
return { timing: "responsive", score: 1 };
|
|
49
|
+
}
|
|
50
|
+
return { timing: "normal", score: 0 };
|
|
51
|
+
}
|
|
52
|
+
function inferTopic(text) {
|
|
53
|
+
const lower = text.toLowerCase();
|
|
54
|
+
let bestTopic = "general";
|
|
55
|
+
let bestScore = 0;
|
|
56
|
+
for (const [topic, patterns] of Object.entries(TOPIC_PATTERNS)) {
|
|
57
|
+
const score = patterns.filter((p) => lower.includes(p)).length;
|
|
58
|
+
if (score > bestScore) {
|
|
59
|
+
bestScore = score;
|
|
60
|
+
bestTopic = topic;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { topic: bestTopic, score: bestScore };
|
|
64
|
+
}
|
|
65
|
+
export function draftRelationshipFromDream(input) {
|
|
66
|
+
const unsupportedClaims = [];
|
|
67
|
+
const replyEntries = input.chronicleEntries.filter((e) => e.kind === "owner_reply" || e.kind === "user_interaction");
|
|
68
|
+
// No-reply signal: if no owner replies, record cooldown without inventing preference
|
|
69
|
+
if (replyEntries.length === 0) {
|
|
70
|
+
return {
|
|
71
|
+
unsupportedClaims: [],
|
|
72
|
+
cooldown: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Prevent over-inference from single sample
|
|
76
|
+
if (replyEntries.length < 2) {
|
|
77
|
+
unsupportedClaims.push("single_sample_insufficient_for_relationship_inference");
|
|
78
|
+
}
|
|
79
|
+
// Aggregate tone/timing/topic across replies
|
|
80
|
+
const toneVotes = new Map();
|
|
81
|
+
const timingVotes = new Map();
|
|
82
|
+
const topicVotes = new Map();
|
|
83
|
+
const sourceRefs = [];
|
|
84
|
+
for (const entry of replyEntries) {
|
|
85
|
+
const tone = inferTone(entry.summary);
|
|
86
|
+
toneVotes.set(tone.tone, (toneVotes.get(tone.tone) ?? 0) + tone.score);
|
|
87
|
+
const timing = inferTiming(entry.summary);
|
|
88
|
+
timingVotes.set(timing.timing, (timingVotes.get(timing.timing) ?? 0) + timing.score);
|
|
89
|
+
const topic = inferTopic(entry.summary);
|
|
90
|
+
topicVotes.set(topic.topic, (topicVotes.get(topic.topic) ?? 0) + topic.score);
|
|
91
|
+
sourceRefs.push(entry.id);
|
|
92
|
+
}
|
|
93
|
+
const topTone = Array.from(toneVotes.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
94
|
+
const topTiming = Array.from(timingVotes.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
95
|
+
const topTopic = Array.from(topicVotes.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
96
|
+
// Compute confidence based on sample size and signal strength
|
|
97
|
+
const sampleSize = replyEntries.length;
|
|
98
|
+
const signalStrength = Math.max(topTone?.[1] ?? 0, topTiming?.[1] ?? 0, topTopic?.[1] ?? 0);
|
|
99
|
+
const confidence = Math.min(0.9, 0.3 + sampleSize * 0.05 + signalStrength * 0.05);
|
|
100
|
+
const toneDelta = topTone && topTone[1] > 0
|
|
101
|
+
? `tone_observed_${topTone[0]}`
|
|
102
|
+
: undefined;
|
|
103
|
+
const timingDelta = topTiming && topTiming[1] > 0
|
|
104
|
+
? `timing_observed_${topTiming[0]}`
|
|
105
|
+
: undefined;
|
|
106
|
+
const topicDelta = topTopic && topTopic[1] > 0
|
|
107
|
+
? `topic_observed_${topTopic[0]}`
|
|
108
|
+
: undefined;
|
|
109
|
+
return {
|
|
110
|
+
proposal: {
|
|
111
|
+
toneDelta,
|
|
112
|
+
timingDelta,
|
|
113
|
+
topicDelta,
|
|
114
|
+
sourceRefs: sourceRefs.slice(0, 20),
|
|
115
|
+
confidence: Number(confidence.toFixed(2)),
|
|
116
|
+
},
|
|
117
|
+
unsupportedClaims,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream input sampler.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: when evidence count exceeds threshold, sample recent 7 days
|
|
5
|
+
* plus key events (outreach, owner reply, goal milestone, high-confidence refs).
|
|
6
|
+
* Goal: prevent token/cost explosion before LLM stage.
|
|
7
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
8
|
+
*/
|
|
9
|
+
export interface SamplerInput {
|
|
10
|
+
evidenceSummaries: Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
kind?: string;
|
|
15
|
+
confidence?: number;
|
|
16
|
+
}>;
|
|
17
|
+
chronicleSummaries: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}>;
|
|
22
|
+
evidenceLimit?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface SamplerResult {
|
|
25
|
+
sampledEvidenceIds: string[];
|
|
26
|
+
sampledChronicleIds: string[];
|
|
27
|
+
droppedCount: number;
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function sampleDreamInput(input: SamplerInput): SamplerResult;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream input sampler.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: when evidence count exceeds threshold, sample recent 7 days
|
|
5
|
+
* plus key events (outreach, owner reply, goal milestone, high-confidence refs).
|
|
6
|
+
* Goal: prevent token/cost explosion before LLM stage.
|
|
7
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_EVIDENCE_LIMIT = 1000;
|
|
10
|
+
const RECENT_DAYS = 7;
|
|
11
|
+
function isWithinDays(createdAt, days) {
|
|
12
|
+
const then = new Date(createdAt).getTime();
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
return now - then <= days * 24 * 60 * 60 * 1000;
|
|
15
|
+
}
|
|
16
|
+
function isKeyEvent(item) {
|
|
17
|
+
if (!item.kind)
|
|
18
|
+
return false;
|
|
19
|
+
const keyKinds = [
|
|
20
|
+
"outreach",
|
|
21
|
+
"owner_reply",
|
|
22
|
+
"goal_milestone",
|
|
23
|
+
"delivery",
|
|
24
|
+
"quiet_reflection",
|
|
25
|
+
];
|
|
26
|
+
return keyKinds.includes(item.kind);
|
|
27
|
+
}
|
|
28
|
+
function isHighConfidence(item) {
|
|
29
|
+
return (item.confidence ?? 0) >= 0.7;
|
|
30
|
+
}
|
|
31
|
+
export function sampleDreamInput(input) {
|
|
32
|
+
const limit = input.evidenceLimit ?? DEFAULT_EVIDENCE_LIMIT;
|
|
33
|
+
// Priority: recent 7 days + key events + high confidence
|
|
34
|
+
const withPriority = input.evidenceSummaries.map((e) => ({
|
|
35
|
+
...e,
|
|
36
|
+
priority: (isWithinDays(e.createdAt, RECENT_DAYS) ? 4 : 0) +
|
|
37
|
+
(isKeyEvent(e) ? 2 : 0) +
|
|
38
|
+
(isHighConfidence(e) ? 1 : 0),
|
|
39
|
+
}));
|
|
40
|
+
// Sort by priority desc, then createdAt desc
|
|
41
|
+
withPriority.sort((a, b) => {
|
|
42
|
+
if (b.priority !== a.priority)
|
|
43
|
+
return b.priority - a.priority;
|
|
44
|
+
return b.createdAt.localeCompare(a.createdAt);
|
|
45
|
+
});
|
|
46
|
+
const sampledEvidence = withPriority.slice(0, limit);
|
|
47
|
+
const sampledEvidenceIds = sampledEvidence.map((e) => e.id);
|
|
48
|
+
const droppedCount = input.evidenceSummaries.length - sampledEvidence.length;
|
|
49
|
+
// Chronicle is usually small; keep all unless it also exceeds limit
|
|
50
|
+
let sampledChronicleIds = input.chronicleSummaries.map((c) => c.id);
|
|
51
|
+
if (sampledChronicleIds.length > limit) {
|
|
52
|
+
sampledChronicleIds = sampledChronicleIds
|
|
53
|
+
.sort((a, b) => b.localeCompare(a))
|
|
54
|
+
.slice(0, limit);
|
|
55
|
+
}
|
|
56
|
+
const reason = droppedCount > 0
|
|
57
|
+
? `sampled_${sampledEvidenceIds.length}_of_${input.evidenceSummaries.length}_evidence;priority_recent+key+confidence`
|
|
58
|
+
: "no_sampling_needed";
|
|
59
|
+
return {
|
|
60
|
+
sampledEvidenceIds,
|
|
61
|
+
sampledChronicleIds,
|
|
62
|
+
droppedCount,
|
|
63
|
+
reason,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream System core types.
|
|
3
|
+
*
|
|
4
|
+
* Dream is an async memory consolidation job. It reads source-backed life evidence,
|
|
5
|
+
* session chronicle, and existing memory store, then produces a candidate MemoryStore
|
|
6
|
+
* with canonical entries, insights, and optional narrative/relationship update proposals.
|
|
7
|
+
*
|
|
8
|
+
* Contract: input store is immutable; output is always candidate until validated and accepted.
|
|
9
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
10
|
+
*/
|
|
11
|
+
import type { CanonicalMemoryEntry, DreamInsight } from "../storage/memory-store/memory-store-lifecycle.js";
|
|
12
|
+
export type { DreamInsight };
|
|
13
|
+
export type DreamTriggerKind = "scheduled" | "evidence_threshold" | "manual" | "maintenance" | "quiet_completion";
|
|
14
|
+
export type DreamRunStatus = "queued" | "running" | "completed" | "skipped" | "failed";
|
|
15
|
+
export type DreamOutputStatus = "candidate" | "accepted" | "archived" | "partial";
|
|
16
|
+
export type DreamMode = "rules_only" | "hybrid_llm" | "model_skipped";
|
|
17
|
+
export interface DreamRun {
|
|
18
|
+
runId: string;
|
|
19
|
+
traceId: string;
|
|
20
|
+
triggerKind: DreamTriggerKind;
|
|
21
|
+
status: DreamRunStatus;
|
|
22
|
+
mode: DreamMode;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
finishedAt?: string;
|
|
25
|
+
inputMemoryStoreId?: string;
|
|
26
|
+
outputMemoryStoreId?: string;
|
|
27
|
+
fallbackReason?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ToolExperienceSummary {
|
|
30
|
+
connectorId: string;
|
|
31
|
+
capabilityId: string;
|
|
32
|
+
outcome: string;
|
|
33
|
+
count: number;
|
|
34
|
+
lastRecordedAt: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DreamInputBundle {
|
|
37
|
+
evidenceRefs: string[];
|
|
38
|
+
chronicleEntryIds: string[];
|
|
39
|
+
activeMemoryStoreId?: string;
|
|
40
|
+
narrativeSnapshotId?: string;
|
|
41
|
+
relationshipSnapshotId?: string;
|
|
42
|
+
goalSnapshotIds: string[];
|
|
43
|
+
toolExperienceSummaries?: ToolExperienceSummary[];
|
|
44
|
+
inputCounts: {
|
|
45
|
+
evidence: number;
|
|
46
|
+
chronicle: number;
|
|
47
|
+
memoryEntries: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface DreamNarrativeUpdate {
|
|
51
|
+
focus: string;
|
|
52
|
+
progressAdditions: string[];
|
|
53
|
+
nextIntent: string;
|
|
54
|
+
confidenceDelta: number;
|
|
55
|
+
sourceRefs: string[];
|
|
56
|
+
unsupportedClaims: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface DreamRelationshipUpdate {
|
|
59
|
+
toneDelta?: string;
|
|
60
|
+
timingDelta?: string;
|
|
61
|
+
topicDelta?: string;
|
|
62
|
+
sourceRefs: string[];
|
|
63
|
+
confidence: number;
|
|
64
|
+
}
|
|
65
|
+
export interface DreamOutputValidation {
|
|
66
|
+
schemaValid: boolean;
|
|
67
|
+
sourceGrounded: boolean;
|
|
68
|
+
sensitivityClean: boolean;
|
|
69
|
+
unsupportedClaims: string[];
|
|
70
|
+
errors: string[];
|
|
71
|
+
checkedAt: string;
|
|
72
|
+
}
|
|
73
|
+
export interface DreamOutput {
|
|
74
|
+
outputId: string;
|
|
75
|
+
runId: string;
|
|
76
|
+
status: DreamOutputStatus;
|
|
77
|
+
inputMemoryStoreId?: string;
|
|
78
|
+
canonicalEntries: CanonicalMemoryEntry[];
|
|
79
|
+
insights: DreamInsight[];
|
|
80
|
+
narrativeUpdate?: DreamNarrativeUpdate;
|
|
81
|
+
relationshipUpdate?: DreamRelationshipUpdate;
|
|
82
|
+
validation: DreamOutputValidation;
|
|
83
|
+
}
|
|
84
|
+
export interface DreamTrace {
|
|
85
|
+
traceId: string;
|
|
86
|
+
runId: string;
|
|
87
|
+
startedAt: string;
|
|
88
|
+
finishedAt: string;
|
|
89
|
+
durationMs: number;
|
|
90
|
+
llmCostUsd?: number;
|
|
91
|
+
inputCounts: DreamInputBundle["inputCounts"];
|
|
92
|
+
fallbackReason?: string;
|
|
93
|
+
validationErrors?: string[];
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
sensitivityFailure?: boolean;
|
|
96
|
+
}
|
|
97
|
+
export interface DreamRunResult {
|
|
98
|
+
runId: string;
|
|
99
|
+
status: DreamRunStatus;
|
|
100
|
+
output?: DreamOutput;
|
|
101
|
+
trace: DreamTrace;
|
|
102
|
+
fallbackReason?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface DreamStatePort {
|
|
105
|
+
loadDreamInputs(query: {
|
|
106
|
+
timeWindowDays?: number;
|
|
107
|
+
evidenceLimit?: number;
|
|
108
|
+
}): Promise<DreamInputBundle>;
|
|
109
|
+
writeDreamOutput(output: DreamOutput): Promise<{
|
|
110
|
+
outputId: string;
|
|
111
|
+
status: "acknowledged" | "degraded";
|
|
112
|
+
}>;
|
|
113
|
+
markDreamOutputLifecycle(input: {
|
|
114
|
+
outputId: string;
|
|
115
|
+
newStatus: DreamOutputStatus;
|
|
116
|
+
validation?: DreamOutputValidation;
|
|
117
|
+
updatedAt: string;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
outputId: string;
|
|
120
|
+
status: "acknowledged" | "degraded";
|
|
121
|
+
}>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Brand type: evidence bundle that has passed through RedactionGate.
|
|
125
|
+
* TypeScript prevents passing un-redacted data to ModelAssistPort.
|
|
126
|
+
*/
|
|
127
|
+
export interface RedactedEvidenceBundle {
|
|
128
|
+
readonly _brand: "redacted";
|
|
129
|
+
readonly evidence: readonly string[];
|
|
130
|
+
readonly chronicle: readonly string[];
|
|
131
|
+
readonly memory?: readonly string[];
|
|
132
|
+
}
|
|
133
|
+
export interface ModelAssistPort {
|
|
134
|
+
/** Only accepts RedactedEvidenceBundle — TypeScript enforces prior redaction (DR-027). */
|
|
135
|
+
extractInsights(input: RedactedEvidenceBundle): Promise<{
|
|
136
|
+
insights: DreamInsight[];
|
|
137
|
+
narrativeUpdate?: DreamNarrativeUpdate;
|
|
138
|
+
relationshipUpdate?: DreamRelationshipUpdate;
|
|
139
|
+
unsupportedClaims: string[];
|
|
140
|
+
costUsd?: number;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
/** @deprecated Use ModelAssistPort with RedactedEvidenceBundle (DR-027). */
|
|
144
|
+
export interface DreamModelPort {
|
|
145
|
+
extractInsights(input: {
|
|
146
|
+
sampledEvidence: string[];
|
|
147
|
+
chronicleSummary: string;
|
|
148
|
+
activeMemorySummary?: string;
|
|
149
|
+
redacted: boolean;
|
|
150
|
+
}): Promise<{
|
|
151
|
+
insights: DreamInsight[];
|
|
152
|
+
narrativeUpdate?: DreamNarrativeUpdate;
|
|
153
|
+
relationshipUpdate?: DreamRelationshipUpdate;
|
|
154
|
+
unsupportedClaims: string[];
|
|
155
|
+
costUsd?: number;
|
|
156
|
+
}>;
|
|
157
|
+
}
|
|
158
|
+
export interface DreamTracePort {
|
|
159
|
+
recordDreamTrace(trace: DreamTrace): Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
export interface DreamBudgetPort {
|
|
162
|
+
checkBudget(costEstimateUsd: number): Promise<{
|
|
163
|
+
allowed: boolean;
|
|
164
|
+
remainingUsd: number;
|
|
165
|
+
}>;
|
|
166
|
+
}
|
|
167
|
+
export interface DreamEngineInput {
|
|
168
|
+
runId: string;
|
|
169
|
+
traceId: string;
|
|
170
|
+
triggerKind: DreamTriggerKind;
|
|
171
|
+
statePort: DreamStatePort;
|
|
172
|
+
/** @deprecated Use modelAssistPort with RedactedEvidenceBundle (DR-027). */
|
|
173
|
+
modelPort?: DreamModelPort;
|
|
174
|
+
/**
|
|
175
|
+
* v7 ModelAssistPort — requires RedactedEvidenceBundle (DR-027).
|
|
176
|
+
* If both modelAssistPort and modelPort are provided, modelAssistPort takes precedence.
|
|
177
|
+
*/
|
|
178
|
+
modelAssistPort?: ModelAssistPort;
|
|
179
|
+
tracePort?: DreamTracePort;
|
|
180
|
+
budgetPort?: DreamBudgetPort;
|
|
181
|
+
options?: {
|
|
182
|
+
timeWindowDays?: number;
|
|
183
|
+
evidenceLimit?: number;
|
|
184
|
+
maxCanonicalEntries?: number;
|
|
185
|
+
operatorTimeoutMs?: number;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream System core types.
|
|
3
|
+
*
|
|
4
|
+
* Dream is an async memory consolidation job. It reads source-backed life evidence,
|
|
5
|
+
* session chronicle, and existing memory store, then produces a candidate MemoryStore
|
|
6
|
+
* with canonical entries, insights, and optional narrative/relationship update proposals.
|
|
7
|
+
*
|
|
8
|
+
* Contract: input store is immutable; output is always candidate until validated and accepted.
|
|
9
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
10
|
+
*/
|
|
11
|
+
export {};
|