@haaaiawd/second-nature 0.1.39 → 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/index.js +1270 -1270
- package/openclaw.plugin.json +29 -29
- package/package.json +55 -55
- package/runtime/cli/commands/connector-init.js +11 -4
- package/runtime/cli/index.js +6 -1
- package/runtime/cli/ops/heartbeat-surface.d.ts +75 -75
- package/runtime/cli/ops/heartbeat-surface.js +97 -97
- package/runtime/cli/ops/ops-router.js +1428 -1428
- package/runtime/cli/ops/workspace-heartbeat-runner.js +236 -236
- package/runtime/connectors/services/connector-executor-adapter.js +192 -41
- package/runtime/core/second-nature/guidance/apply-guidance.d.ts +12 -12
- package/runtime/core/second-nature/guidance/apply-guidance.js +15 -15
- package/runtime/core/second-nature/guidance/user-reply-continuity.d.ts +50 -50
- package/runtime/core/second-nature/guidance/user-reply-continuity.js +89 -89
- package/runtime/core/second-nature/orchestrator/intent-planner.js +15 -0
- package/runtime/core/second-nature/runtime/service-entry.d.ts +39 -39
- package/runtime/core/second-nature/runtime/service-entry.js +44 -44
- package/runtime/dream/dream-engine.d.ts +14 -14
- package/runtime/dream/dream-engine.js +306 -306
- package/runtime/dream/dream-input-loader.d.ts +37 -37
- package/runtime/dream/dream-input-loader.js +150 -150
- package/runtime/dream/dream-scheduler.d.ts +75 -75
- package/runtime/dream/dream-scheduler.js +131 -131
- package/runtime/dream/index.d.ts +16 -16
- package/runtime/dream/index.js +14 -14
- package/runtime/dream/insight-extractor.d.ts +32 -32
- package/runtime/dream/insight-extractor.js +135 -135
- package/runtime/dream/memory-consolidator.d.ts +45 -45
- package/runtime/dream/memory-consolidator.js +140 -140
- package/runtime/dream/narrative-update-proposal.d.ts +34 -34
- package/runtime/dream/narrative-update-proposal.js +83 -83
- package/runtime/dream/output-validator.d.ts +20 -20
- package/runtime/dream/output-validator.js +110 -110
- package/runtime/dream/redaction-gate.d.ts +31 -31
- package/runtime/dream/redaction-gate.js +109 -109
- package/runtime/dream/relationship-update-proposal.d.ts +27 -27
- package/runtime/dream/relationship-update-proposal.js +119 -119
- package/runtime/dream/sampler.d.ts +30 -30
- package/runtime/dream/sampler.js +65 -65
- package/runtime/dream/types.d.ts +187 -187
- package/runtime/dream/types.js +11 -11
- package/runtime/guidance/fallback.js +20 -20
- package/runtime/guidance/guidance-assembler.js +76 -76
- package/runtime/guidance/output-guard.d.ts +13 -13
- package/runtime/guidance/output-guard.js +53 -53
- package/runtime/guidance/template-registry.d.ts +20 -20
- package/runtime/guidance/template-registry.js +93 -93
- package/runtime/guidance/types.d.ts +98 -98
- package/runtime/observability/projections/guidance-audit.js +38 -38
|
@@ -1,306 +1,306 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dream Engine — orchestrates the hybrid memory consolidation pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline: load inputs → consolidate (rules) → sample → redact →
|
|
5
|
-
* optional model insights → merge → validate → write output + trace.
|
|
6
|
-
*
|
|
7
|
-
* Contract:
|
|
8
|
-
* - Input store is never modified.
|
|
9
|
-
* - Output is always candidate until validation passes and lifecycle port accepts it.
|
|
10
|
-
* - Budget/redaction/timeout failures degrade gracefully with trace.
|
|
11
|
-
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
12
|
-
*/
|
|
13
|
-
import { consolidateMemory } from "./memory-consolidator.js";
|
|
14
|
-
import { sampleDreamInput } from "./sampler.js";
|
|
15
|
-
import { redactDreamInput } from "./redaction-gate.js";
|
|
16
|
-
import { validateDreamOutput } from "./output-validator.js";
|
|
17
|
-
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30min
|
|
18
|
-
const DEFAULT_MAX_CANONICAL = 200;
|
|
19
|
-
export async function runDream(input) {
|
|
20
|
-
const startedAt = new Date().toISOString();
|
|
21
|
-
const runId = input.runId;
|
|
22
|
-
const traceId = input.traceId;
|
|
23
|
-
const triggerKind = input.triggerKind;
|
|
24
|
-
const options = input.options ?? {};
|
|
25
|
-
const operatorTimeoutMs = options.operatorTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
26
|
-
// ─── 1. Load inputs ────────────────────────────────────────────────────────
|
|
27
|
-
const inputBundle = await input.statePort.loadDreamInputs({
|
|
28
|
-
timeWindowDays: options.timeWindowDays ?? 30,
|
|
29
|
-
evidenceLimit: options.evidenceLimit ?? 1000,
|
|
30
|
-
});
|
|
31
|
-
if (inputBundle.evidenceRefs.length === 0 &&
|
|
32
|
-
inputBundle.chronicleEntryIds.length === 0 &&
|
|
33
|
-
(inputBundle.inputCounts.memoryEntries ?? 0) === 0) {
|
|
34
|
-
const trace = buildTrace({
|
|
35
|
-
traceId,
|
|
36
|
-
runId,
|
|
37
|
-
startedAt,
|
|
38
|
-
inputCounts: inputBundle.inputCounts,
|
|
39
|
-
fallbackReason: "no_inputs",
|
|
40
|
-
});
|
|
41
|
-
await input.tracePort?.recordDreamTrace(trace);
|
|
42
|
-
return {
|
|
43
|
-
runId,
|
|
44
|
-
status: "skipped",
|
|
45
|
-
trace,
|
|
46
|
-
fallbackReason: "no_inputs",
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// ─── 2. Rules consolidate ──────────────────────────────────────────────────
|
|
50
|
-
// For the rules stage we use placeholder summaries derived from refs.
|
|
51
|
-
// Real integration would load actual summaries from state ports.
|
|
52
|
-
const evidenceSummaries = inputBundle.evidenceRefs.map((ref, i) => ({
|
|
53
|
-
id: ref,
|
|
54
|
-
summary: `evidence:${ref}`,
|
|
55
|
-
sourceRefs: [{ sourceId: ref, kind: "evidence", url: undefined }],
|
|
56
|
-
createdAt: new Date().toISOString(),
|
|
57
|
-
}));
|
|
58
|
-
const chronicleSummaries = inputBundle.chronicleEntryIds.map((id) => ({
|
|
59
|
-
id,
|
|
60
|
-
summary: `chronicle:${id}`,
|
|
61
|
-
sourceRefs: [{ sourceId: id, kind: "chronicle", url: undefined }],
|
|
62
|
-
createdAt: new Date().toISOString(),
|
|
63
|
-
}));
|
|
64
|
-
const toolExperienceSummaries = (inputBundle.toolExperienceSummaries ?? []).map((te) => ({
|
|
65
|
-
id: `${te.connectorId}:${te.capabilityId}:${te.outcome}`,
|
|
66
|
-
summary: `tool_experience:${te.connectorId}:${te.capabilityId}:${te.outcome}:count=${te.count}`,
|
|
67
|
-
sourceRefs: [{ sourceId: `tool_exp:${te.connectorId}:${te.capabilityId}`, kind: "tool_experience", url: undefined }],
|
|
68
|
-
createdAt: te.lastRecordedAt,
|
|
69
|
-
}));
|
|
70
|
-
const consolidation = consolidateMemory({
|
|
71
|
-
evidenceSummaries,
|
|
72
|
-
chronicleSummaries,
|
|
73
|
-
toolExperienceSummaries,
|
|
74
|
-
existingEntries: [], // In real use, load from activeMemoryStoreId
|
|
75
|
-
});
|
|
76
|
-
// ─── 3. Sample ─────────────────────────────────────────────────────────────
|
|
77
|
-
const sampling = sampleDreamInput({
|
|
78
|
-
evidenceSummaries: evidenceSummaries.map((e) => ({
|
|
79
|
-
id: e.id,
|
|
80
|
-
summary: e.summary,
|
|
81
|
-
createdAt: e.createdAt,
|
|
82
|
-
})),
|
|
83
|
-
chronicleSummaries: chronicleSummaries.map((c) => ({
|
|
84
|
-
id: c.id,
|
|
85
|
-
summary: c.summary,
|
|
86
|
-
createdAt: c.createdAt,
|
|
87
|
-
})),
|
|
88
|
-
evidenceLimit: options.evidenceLimit,
|
|
89
|
-
});
|
|
90
|
-
// ─── 4. Redaction ──────────────────────────────────────────────────────────
|
|
91
|
-
const redaction = redactDreamInput({
|
|
92
|
-
evidenceSummaries: sampling.sampledEvidenceIds.map((id) => evidenceSummaries.find((e) => e.id === id)?.summary ?? id),
|
|
93
|
-
chronicleSummaries: sampling.sampledChronicleIds.map((id) => chronicleSummaries.find((c) => c.id === id)?.summary ?? id),
|
|
94
|
-
});
|
|
95
|
-
if (!redaction.allowed) {
|
|
96
|
-
const output = buildOutput({
|
|
97
|
-
runId,
|
|
98
|
-
inputMemoryStoreId: inputBundle.activeMemoryStoreId,
|
|
99
|
-
canonicalEntries: consolidation.entries.slice(0, options.maxCanonicalEntries ?? DEFAULT_MAX_CANONICAL),
|
|
100
|
-
insights: [],
|
|
101
|
-
validation: {
|
|
102
|
-
schemaValid: true,
|
|
103
|
-
sourceGrounded: true,
|
|
104
|
-
sensitivityClean: false,
|
|
105
|
-
unsupportedClaims: [],
|
|
106
|
-
errors: [redaction.blockedReason ?? "redaction_failed"],
|
|
107
|
-
checkedAt: new Date().toISOString(),
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
output.status = "archived";
|
|
111
|
-
await input.statePort.writeDreamOutput(output);
|
|
112
|
-
// DR-023: redaction failure is a validation failure → archived lifecycle.
|
|
113
|
-
await input.statePort.markDreamOutputLifecycle({
|
|
114
|
-
outputId: output.outputId,
|
|
115
|
-
newStatus: "archived",
|
|
116
|
-
validation: output.validation,
|
|
117
|
-
updatedAt: new Date().toISOString(),
|
|
118
|
-
});
|
|
119
|
-
const trace = buildTrace({
|
|
120
|
-
traceId,
|
|
121
|
-
runId,
|
|
122
|
-
startedAt,
|
|
123
|
-
inputCounts: inputBundle.inputCounts,
|
|
124
|
-
fallbackReason: redaction.blockedReason ?? "redaction_failed",
|
|
125
|
-
sensitivityFailure: true,
|
|
126
|
-
});
|
|
127
|
-
await input.tracePort?.recordDreamTrace(trace);
|
|
128
|
-
return {
|
|
129
|
-
runId,
|
|
130
|
-
status: "completed",
|
|
131
|
-
output,
|
|
132
|
-
trace,
|
|
133
|
-
fallbackReason: redaction.blockedReason ?? "redaction_failed",
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
// ─── 5. Budget gate ────────────────────────────────────────────────────────
|
|
137
|
-
let modelResult;
|
|
138
|
-
let mode = "rules_only";
|
|
139
|
-
let fallbackReason;
|
|
140
|
-
let llmCostUsd;
|
|
141
|
-
if ((input.modelAssistPort || input.modelPort) && input.budgetPort) {
|
|
142
|
-
const budgetCheck = await input.budgetPort.checkBudget(0.5);
|
|
143
|
-
if (budgetCheck.allowed) {
|
|
144
|
-
// ─── 6. Model insights ─────────────────────────────────────────────────
|
|
145
|
-
try {
|
|
146
|
-
let modelPromise;
|
|
147
|
-
if (input.modelAssistPort) {
|
|
148
|
-
// DR-027: ModelAssistPort requires RedactedEvidenceBundle brand type.
|
|
149
|
-
// Evidence already redacted by redactDreamInput above; construct brand
|
|
150
|
-
// bundle directly to avoid double-redaction.
|
|
151
|
-
const redactedBundle = {
|
|
152
|
-
_brand: "redacted",
|
|
153
|
-
evidence: redaction.redactedEvidence,
|
|
154
|
-
chronicle: redaction.redactedChronicle,
|
|
155
|
-
memory: redaction.redactedMemory,
|
|
156
|
-
};
|
|
157
|
-
modelPromise = input.modelAssistPort.extractInsights(redactedBundle);
|
|
158
|
-
}
|
|
159
|
-
else if (input.modelPort) {
|
|
160
|
-
// Deprecated path: DreamModelPort accepts plain object (backward compat).
|
|
161
|
-
modelPromise = input.modelPort.extractInsights({
|
|
162
|
-
sampledEvidence: redaction.redactedEvidence,
|
|
163
|
-
chronicleSummary: redaction.redactedChronicle.join("\n"),
|
|
164
|
-
redacted: true,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
if (!fallbackReason) {
|
|
168
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
169
|
-
setTimeout(() => reject(new Error("model_timeout")), operatorTimeoutMs);
|
|
170
|
-
});
|
|
171
|
-
modelResult = await Promise.race([modelPromise, timeoutPromise]);
|
|
172
|
-
mode = "hybrid_llm";
|
|
173
|
-
llmCostUsd = modelResult.costUsd;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
178
|
-
if (msg.includes("timeout")) {
|
|
179
|
-
fallbackReason = "model_timeout";
|
|
180
|
-
mode = "model_skipped";
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
fallbackReason = "model_error";
|
|
184
|
-
mode = "model_skipped";
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
fallbackReason = "budget_exceeded";
|
|
190
|
-
mode = "rules_only";
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
fallbackReason = "model_port_unavailable";
|
|
195
|
-
mode = "rules_only";
|
|
196
|
-
}
|
|
197
|
-
// ─── 7. Merge ──────────────────────────────────────────────────────────────
|
|
198
|
-
const canonicalEntries = consolidation.entries.slice(0, options.maxCanonicalEntries ?? DEFAULT_MAX_CANONICAL);
|
|
199
|
-
const insights = modelResult?.insights ?? [];
|
|
200
|
-
const output = buildOutput({
|
|
201
|
-
runId,
|
|
202
|
-
inputMemoryStoreId: inputBundle.activeMemoryStoreId,
|
|
203
|
-
canonicalEntries,
|
|
204
|
-
insights,
|
|
205
|
-
narrativeUpdate: modelResult?.narrativeUpdate,
|
|
206
|
-
relationshipUpdate: modelResult?.relationshipUpdate,
|
|
207
|
-
validation: {
|
|
208
|
-
schemaValid: true,
|
|
209
|
-
sourceGrounded: true,
|
|
210
|
-
sensitivityClean: true,
|
|
211
|
-
unsupportedClaims: modelResult?.unsupportedClaims ?? [],
|
|
212
|
-
errors: [],
|
|
213
|
-
checkedAt: new Date().toISOString(),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
// ─── 8. Validate ───────────────────────────────────────────────────────────
|
|
217
|
-
const toolExperienceIds = toolExperienceSummaries.map((t) => t.sourceRefs[0].sourceId);
|
|
218
|
-
const validation = validateDreamOutput({
|
|
219
|
-
output,
|
|
220
|
-
inputEvidenceIds: inputBundle.evidenceRefs,
|
|
221
|
-
inputChronicleIds: inputBundle.chronicleEntryIds,
|
|
222
|
-
inputToolExperienceIds: toolExperienceIds,
|
|
223
|
-
});
|
|
224
|
-
// Update output with validation result
|
|
225
|
-
output.validation = validation.validation;
|
|
226
|
-
let outputStatus = "candidate";
|
|
227
|
-
if (!validation.eligible) {
|
|
228
|
-
outputStatus = "archived";
|
|
229
|
-
// If model failed but rules produced something, mark partial
|
|
230
|
-
if (fallbackReason === "model_timeout") {
|
|
231
|
-
outputStatus = "partial";
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
output.status = outputStatus;
|
|
235
|
-
// ─── 9. Write output + lifecycle transition ──────────────────────────────────
|
|
236
|
-
await input.statePort.writeDreamOutput(output);
|
|
237
|
-
// DR-023: validation pass triggers accepted transition.
|
|
238
|
-
if (validation.eligible) {
|
|
239
|
-
try {
|
|
240
|
-
await input.statePort.markDreamOutputLifecycle({
|
|
241
|
-
outputId: output.outputId,
|
|
242
|
-
newStatus: "accepted",
|
|
243
|
-
validation: validation.validation,
|
|
244
|
-
updatedAt: new Date().toISOString(),
|
|
245
|
-
});
|
|
246
|
-
output.status = "accepted";
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
// Transition failed (e.g., concurrent modification); keep candidate in memory
|
|
250
|
-
// but DB remains candidate since transition was rolled back.
|
|
251
|
-
output.status = "candidate";
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
const finishedAt = new Date().toISOString();
|
|
255
|
-
const durationMs = new Date(finishedAt).getTime() - new Date(startedAt).getTime();
|
|
256
|
-
const trace = buildTrace({
|
|
257
|
-
traceId,
|
|
258
|
-
runId,
|
|
259
|
-
startedAt,
|
|
260
|
-
finishedAt,
|
|
261
|
-
durationMs,
|
|
262
|
-
inputCounts: inputBundle.inputCounts,
|
|
263
|
-
fallbackReason,
|
|
264
|
-
llmCostUsd,
|
|
265
|
-
validationErrors: validation.validation.errors,
|
|
266
|
-
timeoutMs: fallbackReason === "model_timeout" ? operatorTimeoutMs : undefined,
|
|
267
|
-
sensitivityFailure: false,
|
|
268
|
-
});
|
|
269
|
-
await input.tracePort?.recordDreamTrace(trace);
|
|
270
|
-
return {
|
|
271
|
-
runId,
|
|
272
|
-
status: "completed",
|
|
273
|
-
output,
|
|
274
|
-
trace,
|
|
275
|
-
fallbackReason,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
279
|
-
function buildOutput(params) {
|
|
280
|
-
return {
|
|
281
|
-
outputId: `dream_output:${crypto.randomUUID()}`,
|
|
282
|
-
runId: params.runId,
|
|
283
|
-
status: "candidate",
|
|
284
|
-
inputMemoryStoreId: params.inputMemoryStoreId,
|
|
285
|
-
canonicalEntries: params.canonicalEntries,
|
|
286
|
-
insights: params.insights,
|
|
287
|
-
narrativeUpdate: params.narrativeUpdate,
|
|
288
|
-
relationshipUpdate: params.relationshipUpdate,
|
|
289
|
-
validation: params.validation,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function buildTrace(params) {
|
|
293
|
-
return {
|
|
294
|
-
traceId: params.traceId,
|
|
295
|
-
runId: params.runId,
|
|
296
|
-
startedAt: params.startedAt,
|
|
297
|
-
finishedAt: params.finishedAt ?? params.startedAt,
|
|
298
|
-
durationMs: params.durationMs ?? 0,
|
|
299
|
-
inputCounts: params.inputCounts,
|
|
300
|
-
fallbackReason: params.fallbackReason,
|
|
301
|
-
llmCostUsd: params.llmCostUsd,
|
|
302
|
-
validationErrors: params.validationErrors,
|
|
303
|
-
timeoutMs: params.timeoutMs,
|
|
304
|
-
sensitivityFailure: params.sensitivityFailure,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Dream Engine — orchestrates the hybrid memory consolidation pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline: load inputs → consolidate (rules) → sample → redact →
|
|
5
|
+
* optional model insights → merge → validate → write output + trace.
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - Input store is never modified.
|
|
9
|
+
* - Output is always candidate until validation passes and lifecycle port accepts it.
|
|
10
|
+
* - Budget/redaction/timeout failures degrade gracefully with trace.
|
|
11
|
+
* Test coverage: tests/integration/dream/t7-1-1-dream-pipeline.test.ts
|
|
12
|
+
*/
|
|
13
|
+
import { consolidateMemory } from "./memory-consolidator.js";
|
|
14
|
+
import { sampleDreamInput } from "./sampler.js";
|
|
15
|
+
import { redactDreamInput } from "./redaction-gate.js";
|
|
16
|
+
import { validateDreamOutput } from "./output-validator.js";
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30min
|
|
18
|
+
const DEFAULT_MAX_CANONICAL = 200;
|
|
19
|
+
export async function runDream(input) {
|
|
20
|
+
const startedAt = new Date().toISOString();
|
|
21
|
+
const runId = input.runId;
|
|
22
|
+
const traceId = input.traceId;
|
|
23
|
+
const triggerKind = input.triggerKind;
|
|
24
|
+
const options = input.options ?? {};
|
|
25
|
+
const operatorTimeoutMs = options.operatorTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
26
|
+
// ─── 1. Load inputs ────────────────────────────────────────────────────────
|
|
27
|
+
const inputBundle = await input.statePort.loadDreamInputs({
|
|
28
|
+
timeWindowDays: options.timeWindowDays ?? 30,
|
|
29
|
+
evidenceLimit: options.evidenceLimit ?? 1000,
|
|
30
|
+
});
|
|
31
|
+
if (inputBundle.evidenceRefs.length === 0 &&
|
|
32
|
+
inputBundle.chronicleEntryIds.length === 0 &&
|
|
33
|
+
(inputBundle.inputCounts.memoryEntries ?? 0) === 0) {
|
|
34
|
+
const trace = buildTrace({
|
|
35
|
+
traceId,
|
|
36
|
+
runId,
|
|
37
|
+
startedAt,
|
|
38
|
+
inputCounts: inputBundle.inputCounts,
|
|
39
|
+
fallbackReason: "no_inputs",
|
|
40
|
+
});
|
|
41
|
+
await input.tracePort?.recordDreamTrace(trace);
|
|
42
|
+
return {
|
|
43
|
+
runId,
|
|
44
|
+
status: "skipped",
|
|
45
|
+
trace,
|
|
46
|
+
fallbackReason: "no_inputs",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// ─── 2. Rules consolidate ──────────────────────────────────────────────────
|
|
50
|
+
// For the rules stage we use placeholder summaries derived from refs.
|
|
51
|
+
// Real integration would load actual summaries from state ports.
|
|
52
|
+
const evidenceSummaries = inputBundle.evidenceRefs.map((ref, i) => ({
|
|
53
|
+
id: ref,
|
|
54
|
+
summary: `evidence:${ref}`,
|
|
55
|
+
sourceRefs: [{ sourceId: ref, kind: "evidence", url: undefined }],
|
|
56
|
+
createdAt: new Date().toISOString(),
|
|
57
|
+
}));
|
|
58
|
+
const chronicleSummaries = inputBundle.chronicleEntryIds.map((id) => ({
|
|
59
|
+
id,
|
|
60
|
+
summary: `chronicle:${id}`,
|
|
61
|
+
sourceRefs: [{ sourceId: id, kind: "chronicle", url: undefined }],
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
}));
|
|
64
|
+
const toolExperienceSummaries = (inputBundle.toolExperienceSummaries ?? []).map((te) => ({
|
|
65
|
+
id: `${te.connectorId}:${te.capabilityId}:${te.outcome}`,
|
|
66
|
+
summary: `tool_experience:${te.connectorId}:${te.capabilityId}:${te.outcome}:count=${te.count}`,
|
|
67
|
+
sourceRefs: [{ sourceId: `tool_exp:${te.connectorId}:${te.capabilityId}`, kind: "tool_experience", url: undefined }],
|
|
68
|
+
createdAt: te.lastRecordedAt,
|
|
69
|
+
}));
|
|
70
|
+
const consolidation = consolidateMemory({
|
|
71
|
+
evidenceSummaries,
|
|
72
|
+
chronicleSummaries,
|
|
73
|
+
toolExperienceSummaries,
|
|
74
|
+
existingEntries: [], // In real use, load from activeMemoryStoreId
|
|
75
|
+
});
|
|
76
|
+
// ─── 3. Sample ─────────────────────────────────────────────────────────────
|
|
77
|
+
const sampling = sampleDreamInput({
|
|
78
|
+
evidenceSummaries: evidenceSummaries.map((e) => ({
|
|
79
|
+
id: e.id,
|
|
80
|
+
summary: e.summary,
|
|
81
|
+
createdAt: e.createdAt,
|
|
82
|
+
})),
|
|
83
|
+
chronicleSummaries: chronicleSummaries.map((c) => ({
|
|
84
|
+
id: c.id,
|
|
85
|
+
summary: c.summary,
|
|
86
|
+
createdAt: c.createdAt,
|
|
87
|
+
})),
|
|
88
|
+
evidenceLimit: options.evidenceLimit,
|
|
89
|
+
});
|
|
90
|
+
// ─── 4. Redaction ──────────────────────────────────────────────────────────
|
|
91
|
+
const redaction = redactDreamInput({
|
|
92
|
+
evidenceSummaries: sampling.sampledEvidenceIds.map((id) => evidenceSummaries.find((e) => e.id === id)?.summary ?? id),
|
|
93
|
+
chronicleSummaries: sampling.sampledChronicleIds.map((id) => chronicleSummaries.find((c) => c.id === id)?.summary ?? id),
|
|
94
|
+
});
|
|
95
|
+
if (!redaction.allowed) {
|
|
96
|
+
const output = buildOutput({
|
|
97
|
+
runId,
|
|
98
|
+
inputMemoryStoreId: inputBundle.activeMemoryStoreId,
|
|
99
|
+
canonicalEntries: consolidation.entries.slice(0, options.maxCanonicalEntries ?? DEFAULT_MAX_CANONICAL),
|
|
100
|
+
insights: [],
|
|
101
|
+
validation: {
|
|
102
|
+
schemaValid: true,
|
|
103
|
+
sourceGrounded: true,
|
|
104
|
+
sensitivityClean: false,
|
|
105
|
+
unsupportedClaims: [],
|
|
106
|
+
errors: [redaction.blockedReason ?? "redaction_failed"],
|
|
107
|
+
checkedAt: new Date().toISOString(),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
output.status = "archived";
|
|
111
|
+
await input.statePort.writeDreamOutput(output);
|
|
112
|
+
// DR-023: redaction failure is a validation failure → archived lifecycle.
|
|
113
|
+
await input.statePort.markDreamOutputLifecycle({
|
|
114
|
+
outputId: output.outputId,
|
|
115
|
+
newStatus: "archived",
|
|
116
|
+
validation: output.validation,
|
|
117
|
+
updatedAt: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
const trace = buildTrace({
|
|
120
|
+
traceId,
|
|
121
|
+
runId,
|
|
122
|
+
startedAt,
|
|
123
|
+
inputCounts: inputBundle.inputCounts,
|
|
124
|
+
fallbackReason: redaction.blockedReason ?? "redaction_failed",
|
|
125
|
+
sensitivityFailure: true,
|
|
126
|
+
});
|
|
127
|
+
await input.tracePort?.recordDreamTrace(trace);
|
|
128
|
+
return {
|
|
129
|
+
runId,
|
|
130
|
+
status: "completed",
|
|
131
|
+
output,
|
|
132
|
+
trace,
|
|
133
|
+
fallbackReason: redaction.blockedReason ?? "redaction_failed",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ─── 5. Budget gate ────────────────────────────────────────────────────────
|
|
137
|
+
let modelResult;
|
|
138
|
+
let mode = "rules_only";
|
|
139
|
+
let fallbackReason;
|
|
140
|
+
let llmCostUsd;
|
|
141
|
+
if ((input.modelAssistPort || input.modelPort) && input.budgetPort) {
|
|
142
|
+
const budgetCheck = await input.budgetPort.checkBudget(0.5);
|
|
143
|
+
if (budgetCheck.allowed) {
|
|
144
|
+
// ─── 6. Model insights ─────────────────────────────────────────────────
|
|
145
|
+
try {
|
|
146
|
+
let modelPromise;
|
|
147
|
+
if (input.modelAssistPort) {
|
|
148
|
+
// DR-027: ModelAssistPort requires RedactedEvidenceBundle brand type.
|
|
149
|
+
// Evidence already redacted by redactDreamInput above; construct brand
|
|
150
|
+
// bundle directly to avoid double-redaction.
|
|
151
|
+
const redactedBundle = {
|
|
152
|
+
_brand: "redacted",
|
|
153
|
+
evidence: redaction.redactedEvidence,
|
|
154
|
+
chronicle: redaction.redactedChronicle,
|
|
155
|
+
memory: redaction.redactedMemory,
|
|
156
|
+
};
|
|
157
|
+
modelPromise = input.modelAssistPort.extractInsights(redactedBundle);
|
|
158
|
+
}
|
|
159
|
+
else if (input.modelPort) {
|
|
160
|
+
// Deprecated path: DreamModelPort accepts plain object (backward compat).
|
|
161
|
+
modelPromise = input.modelPort.extractInsights({
|
|
162
|
+
sampledEvidence: redaction.redactedEvidence,
|
|
163
|
+
chronicleSummary: redaction.redactedChronicle.join("\n"),
|
|
164
|
+
redacted: true,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!fallbackReason) {
|
|
168
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
169
|
+
setTimeout(() => reject(new Error("model_timeout")), operatorTimeoutMs);
|
|
170
|
+
});
|
|
171
|
+
modelResult = await Promise.race([modelPromise, timeoutPromise]);
|
|
172
|
+
mode = "hybrid_llm";
|
|
173
|
+
llmCostUsd = modelResult.costUsd;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
178
|
+
if (msg.includes("timeout")) {
|
|
179
|
+
fallbackReason = "model_timeout";
|
|
180
|
+
mode = "model_skipped";
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
fallbackReason = "model_error";
|
|
184
|
+
mode = "model_skipped";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
fallbackReason = "budget_exceeded";
|
|
190
|
+
mode = "rules_only";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
fallbackReason = "model_port_unavailable";
|
|
195
|
+
mode = "rules_only";
|
|
196
|
+
}
|
|
197
|
+
// ─── 7. Merge ──────────────────────────────────────────────────────────────
|
|
198
|
+
const canonicalEntries = consolidation.entries.slice(0, options.maxCanonicalEntries ?? DEFAULT_MAX_CANONICAL);
|
|
199
|
+
const insights = modelResult?.insights ?? [];
|
|
200
|
+
const output = buildOutput({
|
|
201
|
+
runId,
|
|
202
|
+
inputMemoryStoreId: inputBundle.activeMemoryStoreId,
|
|
203
|
+
canonicalEntries,
|
|
204
|
+
insights,
|
|
205
|
+
narrativeUpdate: modelResult?.narrativeUpdate,
|
|
206
|
+
relationshipUpdate: modelResult?.relationshipUpdate,
|
|
207
|
+
validation: {
|
|
208
|
+
schemaValid: true,
|
|
209
|
+
sourceGrounded: true,
|
|
210
|
+
sensitivityClean: true,
|
|
211
|
+
unsupportedClaims: modelResult?.unsupportedClaims ?? [],
|
|
212
|
+
errors: [],
|
|
213
|
+
checkedAt: new Date().toISOString(),
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
// ─── 8. Validate ───────────────────────────────────────────────────────────
|
|
217
|
+
const toolExperienceIds = toolExperienceSummaries.map((t) => t.sourceRefs[0].sourceId);
|
|
218
|
+
const validation = validateDreamOutput({
|
|
219
|
+
output,
|
|
220
|
+
inputEvidenceIds: inputBundle.evidenceRefs,
|
|
221
|
+
inputChronicleIds: inputBundle.chronicleEntryIds,
|
|
222
|
+
inputToolExperienceIds: toolExperienceIds,
|
|
223
|
+
});
|
|
224
|
+
// Update output with validation result
|
|
225
|
+
output.validation = validation.validation;
|
|
226
|
+
let outputStatus = "candidate";
|
|
227
|
+
if (!validation.eligible) {
|
|
228
|
+
outputStatus = "archived";
|
|
229
|
+
// If model failed but rules produced something, mark partial
|
|
230
|
+
if (fallbackReason === "model_timeout") {
|
|
231
|
+
outputStatus = "partial";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
output.status = outputStatus;
|
|
235
|
+
// ─── 9. Write output + lifecycle transition ──────────────────────────────────
|
|
236
|
+
await input.statePort.writeDreamOutput(output);
|
|
237
|
+
// DR-023: validation pass triggers accepted transition.
|
|
238
|
+
if (validation.eligible) {
|
|
239
|
+
try {
|
|
240
|
+
await input.statePort.markDreamOutputLifecycle({
|
|
241
|
+
outputId: output.outputId,
|
|
242
|
+
newStatus: "accepted",
|
|
243
|
+
validation: validation.validation,
|
|
244
|
+
updatedAt: new Date().toISOString(),
|
|
245
|
+
});
|
|
246
|
+
output.status = "accepted";
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Transition failed (e.g., concurrent modification); keep candidate in memory
|
|
250
|
+
// but DB remains candidate since transition was rolled back.
|
|
251
|
+
output.status = "candidate";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const finishedAt = new Date().toISOString();
|
|
255
|
+
const durationMs = new Date(finishedAt).getTime() - new Date(startedAt).getTime();
|
|
256
|
+
const trace = buildTrace({
|
|
257
|
+
traceId,
|
|
258
|
+
runId,
|
|
259
|
+
startedAt,
|
|
260
|
+
finishedAt,
|
|
261
|
+
durationMs,
|
|
262
|
+
inputCounts: inputBundle.inputCounts,
|
|
263
|
+
fallbackReason,
|
|
264
|
+
llmCostUsd,
|
|
265
|
+
validationErrors: validation.validation.errors,
|
|
266
|
+
timeoutMs: fallbackReason === "model_timeout" ? operatorTimeoutMs : undefined,
|
|
267
|
+
sensitivityFailure: false,
|
|
268
|
+
});
|
|
269
|
+
await input.tracePort?.recordDreamTrace(trace);
|
|
270
|
+
return {
|
|
271
|
+
runId,
|
|
272
|
+
status: "completed",
|
|
273
|
+
output,
|
|
274
|
+
trace,
|
|
275
|
+
fallbackReason,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
279
|
+
function buildOutput(params) {
|
|
280
|
+
return {
|
|
281
|
+
outputId: `dream_output:${crypto.randomUUID()}`,
|
|
282
|
+
runId: params.runId,
|
|
283
|
+
status: "candidate",
|
|
284
|
+
inputMemoryStoreId: params.inputMemoryStoreId,
|
|
285
|
+
canonicalEntries: params.canonicalEntries,
|
|
286
|
+
insights: params.insights,
|
|
287
|
+
narrativeUpdate: params.narrativeUpdate,
|
|
288
|
+
relationshipUpdate: params.relationshipUpdate,
|
|
289
|
+
validation: params.validation,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function buildTrace(params) {
|
|
293
|
+
return {
|
|
294
|
+
traceId: params.traceId,
|
|
295
|
+
runId: params.runId,
|
|
296
|
+
startedAt: params.startedAt,
|
|
297
|
+
finishedAt: params.finishedAt ?? params.startedAt,
|
|
298
|
+
durationMs: params.durationMs ?? 0,
|
|
299
|
+
inputCounts: params.inputCounts,
|
|
300
|
+
fallbackReason: params.fallbackReason,
|
|
301
|
+
llmCostUsd: params.llmCostUsd,
|
|
302
|
+
validationErrors: params.validationErrors,
|
|
303
|
+
timeoutMs: params.timeoutMs,
|
|
304
|
+
sensitivityFailure: params.sensitivityFailure,
|
|
305
|
+
};
|
|
306
|
+
}
|