@joshuaswarren/openclaw-engram 9.0.19 → 9.0.20
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/README.md +1 -0
- package/dist/index.js +332 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
31
31
|
- **Pluggable search** — Choose from six search backends: QMD (hybrid BM25+vector+reranking), LanceDB, Meilisearch, Orama, remote HTTP, or bring your own.
|
|
32
32
|
- **Memory OS features** — Graph recall, temporal memory tree, lifecycle policy, compounding, shared context, memory boxes, and identity continuity can be enabled progressively as your install grows.
|
|
33
33
|
- **Benchmark-first roadmap** — Engram now has an evaluation harness with live shadow recall recording and a CI benchmark delta gate, so memory improvements can be measured and regression-checked instead of argued from anecdotes.
|
|
34
|
+
- **Objective-state foundation** — When `objectiveStateMemoryEnabled` and `objectiveStateSnapshotWritesEnabled` are enabled together, Engram records normalized file, process, and tool outcomes from `agent_end` tool activity into a dedicated objective-state store.
|
|
34
35
|
- **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
|
|
35
36
|
|
|
36
37
|
## Quick Start
|
package/dist/index.js
CHANGED
|
@@ -26056,6 +26056,13 @@ function assertIsoRecordedAt(value) {
|
|
|
26056
26056
|
}
|
|
26057
26057
|
return value;
|
|
26058
26058
|
}
|
|
26059
|
+
function objectiveStateDay(recordedAt) {
|
|
26060
|
+
const day = recordedAt.slice(0, 10);
|
|
26061
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) {
|
|
26062
|
+
throw new Error("recordedAt must start with a valid YYYY-MM-DD date");
|
|
26063
|
+
}
|
|
26064
|
+
return day;
|
|
26065
|
+
}
|
|
26059
26066
|
function optionalStringArray2(value, field) {
|
|
26060
26067
|
if (value === void 0) return void 0;
|
|
26061
26068
|
if (!Array.isArray(value)) throw new Error(`${field} must be an array of strings`);
|
|
@@ -26128,6 +26135,16 @@ function validateObjectiveStateSnapshot(raw) {
|
|
|
26128
26135
|
metadata: validateMetadata(raw.metadata)
|
|
26129
26136
|
};
|
|
26130
26137
|
}
|
|
26138
|
+
async function recordObjectiveStateSnapshot(options) {
|
|
26139
|
+
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
26140
|
+
const validated = validateObjectiveStateSnapshot(options.snapshot);
|
|
26141
|
+
const day = objectiveStateDay(validated.recordedAt);
|
|
26142
|
+
const snapshotsDir = path52.join(rootDir, "snapshots", day);
|
|
26143
|
+
const filePath = path52.join(snapshotsDir, `${validated.snapshotId}.json`);
|
|
26144
|
+
await mkdir34(snapshotsDir, { recursive: true });
|
|
26145
|
+
await writeFile30(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
26146
|
+
return filePath;
|
|
26147
|
+
}
|
|
26131
26148
|
async function getObjectiveStateStoreStatus(options) {
|
|
26132
26149
|
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
26133
26150
|
const snapshotsDir = path52.join(rootDir, "snapshots");
|
|
@@ -28555,6 +28572,303 @@ function parseDuration(duration) {
|
|
|
28555
28572
|
return total || 12;
|
|
28556
28573
|
}
|
|
28557
28574
|
|
|
28575
|
+
// src/objective-state-writers.ts
|
|
28576
|
+
import crypto2 from "crypto";
|
|
28577
|
+
function hashSha256(value) {
|
|
28578
|
+
return crypto2.createHash("sha256").update(value).digest("hex");
|
|
28579
|
+
}
|
|
28580
|
+
function isRecord3(value) {
|
|
28581
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28582
|
+
}
|
|
28583
|
+
function optionalString2(value) {
|
|
28584
|
+
if (typeof value !== "string") return void 0;
|
|
28585
|
+
const trimmed = value.trim();
|
|
28586
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
28587
|
+
}
|
|
28588
|
+
function toolNameTokens(toolName) {
|
|
28589
|
+
if (!toolName) return [];
|
|
28590
|
+
return toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length > 0);
|
|
28591
|
+
}
|
|
28592
|
+
function normalizedToolName(toolName) {
|
|
28593
|
+
return toolNameTokens(toolName).join("_");
|
|
28594
|
+
}
|
|
28595
|
+
function parseToolArguments(value) {
|
|
28596
|
+
if (isRecord3(value)) return value;
|
|
28597
|
+
if (typeof value !== "string") return void 0;
|
|
28598
|
+
try {
|
|
28599
|
+
const parsed = JSON.parse(value);
|
|
28600
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
28601
|
+
} catch {
|
|
28602
|
+
return void 0;
|
|
28603
|
+
}
|
|
28604
|
+
}
|
|
28605
|
+
function extractTextContent(value) {
|
|
28606
|
+
if (typeof value === "string") return value.trim();
|
|
28607
|
+
if (Array.isArray(value)) {
|
|
28608
|
+
return value.map((block) => {
|
|
28609
|
+
if (typeof block === "string") return block.trim();
|
|
28610
|
+
if (isRecord3(block) && block.type === "text" && typeof block.text === "string") {
|
|
28611
|
+
return block.text.trim();
|
|
28612
|
+
}
|
|
28613
|
+
return "";
|
|
28614
|
+
}).filter((item) => item.length > 0).join("\n");
|
|
28615
|
+
}
|
|
28616
|
+
if (isRecord3(value)) {
|
|
28617
|
+
return JSON.stringify(value);
|
|
28618
|
+
}
|
|
28619
|
+
return "";
|
|
28620
|
+
}
|
|
28621
|
+
function parseToolResultPayload(content) {
|
|
28622
|
+
const text = extractTextContent(content);
|
|
28623
|
+
if (text.length === 0) return void 0;
|
|
28624
|
+
try {
|
|
28625
|
+
return JSON.parse(text);
|
|
28626
|
+
} catch {
|
|
28627
|
+
return text;
|
|
28628
|
+
}
|
|
28629
|
+
}
|
|
28630
|
+
function resultHash(value) {
|
|
28631
|
+
if (value === void 0) return void 0;
|
|
28632
|
+
const canonical = typeof value === "string" ? value : JSON.stringify(value);
|
|
28633
|
+
if (!canonical || canonical.length === 0) return void 0;
|
|
28634
|
+
return `sha256:${hashSha256(canonical)}`;
|
|
28635
|
+
}
|
|
28636
|
+
function getToolCallContexts(messages) {
|
|
28637
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
28638
|
+
for (const message of messages) {
|
|
28639
|
+
if (message.role !== "assistant") continue;
|
|
28640
|
+
const toolCalls = message.tool_calls ?? message.toolCalls;
|
|
28641
|
+
if (!Array.isArray(toolCalls)) continue;
|
|
28642
|
+
for (const call of toolCalls) {
|
|
28643
|
+
if (!isRecord3(call)) continue;
|
|
28644
|
+
const toolCallId = optionalString2(call.id) ?? optionalString2(call.toolCallId);
|
|
28645
|
+
if (!toolCallId) continue;
|
|
28646
|
+
const fn = isRecord3(call.function) ? call.function : void 0;
|
|
28647
|
+
const toolName = optionalString2(fn?.name) ?? optionalString2(call.name);
|
|
28648
|
+
const args = parseToolArguments(fn?.arguments) ?? parseToolArguments(call.arguments) ?? parseToolArguments(call.args) ?? parseToolArguments(call.input);
|
|
28649
|
+
contexts.set(toolCallId, { toolCallId, toolName, args });
|
|
28650
|
+
}
|
|
28651
|
+
}
|
|
28652
|
+
return contexts;
|
|
28653
|
+
}
|
|
28654
|
+
function toolCallIdForMessage(message) {
|
|
28655
|
+
return optionalString2(message.tool_call_id) ?? optionalString2(message.toolCallId) ?? optionalString2(message.tool_use_id) ?? optionalString2(message.toolUseId);
|
|
28656
|
+
}
|
|
28657
|
+
function toolNameForMessage(message, context) {
|
|
28658
|
+
return optionalString2(message.name) ?? optionalString2(message.toolName) ?? optionalString2(message.tool) ?? context?.toolName;
|
|
28659
|
+
}
|
|
28660
|
+
function pickString(args, keys) {
|
|
28661
|
+
if (!args) return void 0;
|
|
28662
|
+
for (const key of keys) {
|
|
28663
|
+
const value = optionalString2(args[key]);
|
|
28664
|
+
if (value) return value;
|
|
28665
|
+
}
|
|
28666
|
+
return void 0;
|
|
28667
|
+
}
|
|
28668
|
+
function pickFirstStringArrayValue(args, key) {
|
|
28669
|
+
const value = args?.[key];
|
|
28670
|
+
if (!Array.isArray(value)) return void 0;
|
|
28671
|
+
for (const item of value) {
|
|
28672
|
+
const candidate = optionalString2(item);
|
|
28673
|
+
if (candidate) return candidate;
|
|
28674
|
+
}
|
|
28675
|
+
return void 0;
|
|
28676
|
+
}
|
|
28677
|
+
function fileScopeFromArgs(args) {
|
|
28678
|
+
const destinationPath = pickString(args, ["destination", "dest", "targetPath", "target", "to"]) ?? pickString(args, ["path", "filePath", "workspacePath", "projectPath"]) ?? pickFirstStringArrayValue(args, "paths");
|
|
28679
|
+
const sourcePath = pickString(args, ["source", "src", "from", "oldPath"]);
|
|
28680
|
+
const scope = destinationPath ?? sourcePath;
|
|
28681
|
+
return { scope, sourcePath, destinationPath };
|
|
28682
|
+
}
|
|
28683
|
+
function fileContentHash(args) {
|
|
28684
|
+
const content = pickString(args, ["content", "patch", "diff", "text", "value"]) ?? args?.updates;
|
|
28685
|
+
return resultHash(content);
|
|
28686
|
+
}
|
|
28687
|
+
function inferOutcome(message, parsedPayload) {
|
|
28688
|
+
if (message.isError === true) return "failure";
|
|
28689
|
+
if (isRecord3(parsedPayload)) {
|
|
28690
|
+
if (parsedPayload.partial === true || parsedPayload.status === "partial") return "partial";
|
|
28691
|
+
if (parsedPayload.success === false || parsedPayload.ok === false) return "failure";
|
|
28692
|
+
if (parsedPayload.success === true || parsedPayload.ok === true) return "success";
|
|
28693
|
+
if (typeof parsedPayload.exitCode === "number") {
|
|
28694
|
+
return parsedPayload.exitCode === 0 ? "success" : "failure";
|
|
28695
|
+
}
|
|
28696
|
+
if (optionalString2(parsedPayload.error)) return "failure";
|
|
28697
|
+
if (parsedPayload.status === "error" || parsedPayload.status === "failed") return "failure";
|
|
28698
|
+
if (parsedPayload.status === "ok" || parsedPayload.status === "success") return "success";
|
|
28699
|
+
}
|
|
28700
|
+
if (typeof parsedPayload === "string") {
|
|
28701
|
+
const lowered = parsedPayload.toLowerCase();
|
|
28702
|
+
if (lowered.includes("error") || lowered.includes("failed") || lowered.includes("exception")) {
|
|
28703
|
+
return "failure";
|
|
28704
|
+
}
|
|
28705
|
+
}
|
|
28706
|
+
return "unknown";
|
|
28707
|
+
}
|
|
28708
|
+
function isProcessTool(toolName, args) {
|
|
28709
|
+
const tokens = toolNameTokens(toolName);
|
|
28710
|
+
const normalizedName = normalizedToolName(toolName);
|
|
28711
|
+
if (pickString(args, ["cmd", "command", "script"])) return true;
|
|
28712
|
+
return ["exec", "shell", "bash", "terminal", "run_command", "exec_command"].some(
|
|
28713
|
+
(token) => token.includes("_") ? normalizedName === token : tokens.includes(token)
|
|
28714
|
+
);
|
|
28715
|
+
}
|
|
28716
|
+
function isFileTool(toolName, args) {
|
|
28717
|
+
const tokens = toolNameTokens(toolName);
|
|
28718
|
+
const fileScope = fileScopeFromArgs(args);
|
|
28719
|
+
if (fileScope.scope) return true;
|
|
28720
|
+
return ["file", "path", "patch", "directory", "mkdir", "rename", "move"].some(
|
|
28721
|
+
(token) => tokens.includes(token)
|
|
28722
|
+
);
|
|
28723
|
+
}
|
|
28724
|
+
function inferFileChangeKind(toolName, outcome) {
|
|
28725
|
+
if (outcome === "failure") return "failed";
|
|
28726
|
+
const tokens = toolNameTokens(toolName);
|
|
28727
|
+
if (["delete", "remove", "unlink"].some((token) => tokens.includes(token))) return "deleted";
|
|
28728
|
+
if (["create", "mkdir", "new"].some((token) => tokens.includes(token))) return "created";
|
|
28729
|
+
if (["write", "edit", "patch", "update", "append", "move", "rename"].some((token) => tokens.includes(token))) {
|
|
28730
|
+
return "updated";
|
|
28731
|
+
}
|
|
28732
|
+
return "observed";
|
|
28733
|
+
}
|
|
28734
|
+
function buildFileValueRefs(args, changeKind) {
|
|
28735
|
+
const { sourcePath, destinationPath, scope } = fileScopeFromArgs(args);
|
|
28736
|
+
const contentHash = fileContentHash(args);
|
|
28737
|
+
if (changeKind === "failed") {
|
|
28738
|
+
if (sourcePath && destinationPath && sourcePath !== destinationPath) {
|
|
28739
|
+
return {
|
|
28740
|
+
before: { ref: sourcePath },
|
|
28741
|
+
after: { ref: destinationPath }
|
|
28742
|
+
};
|
|
28743
|
+
}
|
|
28744
|
+
return {
|
|
28745
|
+
before: sourcePath ? { ref: sourcePath } : void 0,
|
|
28746
|
+
after: scope ? { ref: scope } : void 0
|
|
28747
|
+
};
|
|
28748
|
+
}
|
|
28749
|
+
if (changeKind === "deleted") {
|
|
28750
|
+
return {
|
|
28751
|
+
before: scope ? { exists: true, ref: scope } : void 0,
|
|
28752
|
+
after: { exists: false }
|
|
28753
|
+
};
|
|
28754
|
+
}
|
|
28755
|
+
if (changeKind === "created") {
|
|
28756
|
+
return {
|
|
28757
|
+
after: {
|
|
28758
|
+
exists: true,
|
|
28759
|
+
ref: scope,
|
|
28760
|
+
valueHash: contentHash
|
|
28761
|
+
}
|
|
28762
|
+
};
|
|
28763
|
+
}
|
|
28764
|
+
if (sourcePath && destinationPath && sourcePath !== destinationPath) {
|
|
28765
|
+
return {
|
|
28766
|
+
before: { exists: true, ref: sourcePath },
|
|
28767
|
+
after: {
|
|
28768
|
+
exists: true,
|
|
28769
|
+
ref: destinationPath
|
|
28770
|
+
}
|
|
28771
|
+
};
|
|
28772
|
+
}
|
|
28773
|
+
return {
|
|
28774
|
+
after: {
|
|
28775
|
+
exists: true,
|
|
28776
|
+
ref: scope,
|
|
28777
|
+
valueHash: contentHash
|
|
28778
|
+
}
|
|
28779
|
+
};
|
|
28780
|
+
}
|
|
28781
|
+
function summarizeSnapshot(kind, changeKind, toolName, scope) {
|
|
28782
|
+
const action = changeKind === "executed" ? "Executed" : changeKind === "failed" ? "Failed" : changeKind === "created" ? "Created" : changeKind === "deleted" ? "Deleted" : changeKind === "updated" ? "Updated" : "Observed";
|
|
28783
|
+
if (kind === "process") return `${action} process via ${toolName}: ${scope}`;
|
|
28784
|
+
if (kind === "file") return `${action} file via ${toolName}: ${scope}`;
|
|
28785
|
+
return `${action} tool result from ${toolName}: ${scope}`;
|
|
28786
|
+
}
|
|
28787
|
+
function buildGenericToolAfterRef(outcome, parsedPayload) {
|
|
28788
|
+
const valueHash = resultHash(parsedPayload);
|
|
28789
|
+
return valueHash ? { valueHash } : { exists: outcome !== "failure" };
|
|
28790
|
+
}
|
|
28791
|
+
function snapshotIdFor(sessionKey, recordedAt, index, toolName, scope) {
|
|
28792
|
+
const digest = crypto2.createHash("sha256").update(`${sessionKey}|${recordedAt}|${index}|${toolName}|${scope}`).digest("hex").slice(0, 12);
|
|
28793
|
+
return `obj-${digest}`;
|
|
28794
|
+
}
|
|
28795
|
+
function deriveObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
28796
|
+
const toolCallsById = getToolCallContexts(options.messages);
|
|
28797
|
+
const snapshots = [];
|
|
28798
|
+
for (const message of options.messages) {
|
|
28799
|
+
if (message.role !== "tool") continue;
|
|
28800
|
+
const toolCallId = toolCallIdForMessage(message);
|
|
28801
|
+
const context = toolCallId ? toolCallsById.get(toolCallId) : void 0;
|
|
28802
|
+
const toolName = toolNameForMessage(message, context);
|
|
28803
|
+
if (!toolName) continue;
|
|
28804
|
+
const parsedPayload = parseToolResultPayload(message.content);
|
|
28805
|
+
const outcome = inferOutcome(message, parsedPayload);
|
|
28806
|
+
const args = context?.args;
|
|
28807
|
+
const command = pickString(args, ["cmd", "command", "script"]);
|
|
28808
|
+
let kind = "tool";
|
|
28809
|
+
let changeKind = outcome === "failure" ? "failed" : "observed";
|
|
28810
|
+
let scope = toolName;
|
|
28811
|
+
let before;
|
|
28812
|
+
let after;
|
|
28813
|
+
if (isProcessTool(toolName, args)) {
|
|
28814
|
+
kind = "process";
|
|
28815
|
+
changeKind = outcome === "failure" ? "failed" : "executed";
|
|
28816
|
+
scope = command ?? toolName;
|
|
28817
|
+
after = { exists: outcome !== "failure", valueHash: resultHash(parsedPayload) };
|
|
28818
|
+
} else if (isFileTool(toolName, args)) {
|
|
28819
|
+
kind = "file";
|
|
28820
|
+
changeKind = inferFileChangeKind(toolName, outcome);
|
|
28821
|
+
const fileScope = fileScopeFromArgs(args);
|
|
28822
|
+
scope = fileScope.scope ?? toolName;
|
|
28823
|
+
const refs = buildFileValueRefs(args, changeKind);
|
|
28824
|
+
before = refs.before;
|
|
28825
|
+
after = refs.after;
|
|
28826
|
+
} else {
|
|
28827
|
+
after = buildGenericToolAfterRef(outcome, parsedPayload);
|
|
28828
|
+
}
|
|
28829
|
+
snapshots.push({
|
|
28830
|
+
schemaVersion: 1,
|
|
28831
|
+
snapshotId: snapshotIdFor(options.sessionKey, options.recordedAt, snapshots.length, toolName, scope),
|
|
28832
|
+
recordedAt: options.recordedAt,
|
|
28833
|
+
sessionKey: options.sessionKey,
|
|
28834
|
+
source: "tool_result",
|
|
28835
|
+
kind,
|
|
28836
|
+
changeKind,
|
|
28837
|
+
scope,
|
|
28838
|
+
summary: summarizeSnapshot(kind, changeKind, toolName, scope),
|
|
28839
|
+
toolName,
|
|
28840
|
+
command,
|
|
28841
|
+
outcome,
|
|
28842
|
+
before,
|
|
28843
|
+
after,
|
|
28844
|
+
tags: ["agent-end", `tool:${toolName}`],
|
|
28845
|
+
metadata: toolCallId ? { toolCallId } : void 0
|
|
28846
|
+
});
|
|
28847
|
+
}
|
|
28848
|
+
return snapshots;
|
|
28849
|
+
}
|
|
28850
|
+
async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
28851
|
+
if (!options.objectiveStateMemoryEnabled || !options.objectiveStateSnapshotWritesEnabled) {
|
|
28852
|
+
return { snapshots: [], filePaths: [] };
|
|
28853
|
+
}
|
|
28854
|
+
const snapshots = deriveObjectiveStateSnapshotsFromAgentMessages({
|
|
28855
|
+
sessionKey: options.sessionKey,
|
|
28856
|
+
recordedAt: options.recordedAt,
|
|
28857
|
+
messages: options.messages
|
|
28858
|
+
});
|
|
28859
|
+
const filePaths = [];
|
|
28860
|
+
for (const snapshot of snapshots) {
|
|
28861
|
+
filePaths.push(
|
|
28862
|
+
await recordObjectiveStateSnapshot({
|
|
28863
|
+
memoryDir: options.memoryDir,
|
|
28864
|
+
objectiveStateStoreDir: options.objectiveStateStoreDir,
|
|
28865
|
+
snapshot
|
|
28866
|
+
})
|
|
28867
|
+
);
|
|
28868
|
+
}
|
|
28869
|
+
return { snapshots, filePaths };
|
|
28870
|
+
}
|
|
28871
|
+
|
|
28558
28872
|
// src/index.ts
|
|
28559
28873
|
import { readFile as readFile39, writeFile as writeFile31 } from "fs/promises";
|
|
28560
28874
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -28698,6 +29012,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
28698
29012
|
try {
|
|
28699
29013
|
const messages = event.messages;
|
|
28700
29014
|
const lastTurn = extractLastTurn(messages);
|
|
29015
|
+
const eventTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
28701
29016
|
if (orchestrator.config.hourlySummariesIncludeToolStats) {
|
|
28702
29017
|
const toolNames = [];
|
|
28703
29018
|
for (const msg of messages) {
|
|
@@ -28716,23 +29031,35 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
28716
29031
|
}
|
|
28717
29032
|
}
|
|
28718
29033
|
}
|
|
28719
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
28720
29034
|
for (const tool of toolNames) {
|
|
28721
|
-
await orchestrator.transcript.appendToolUse({ timestamp:
|
|
29035
|
+
await orchestrator.transcript.appendToolUse({ timestamp: eventTimestamp, sessionKey, tool });
|
|
28722
29036
|
}
|
|
28723
29037
|
}
|
|
29038
|
+
try {
|
|
29039
|
+
await recordObjectiveStateSnapshotsFromAgentMessages({
|
|
29040
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29041
|
+
objectiveStateStoreDir: orchestrator.config.objectiveStateStoreDir,
|
|
29042
|
+
objectiveStateMemoryEnabled: orchestrator.config.objectiveStateMemoryEnabled,
|
|
29043
|
+
objectiveStateSnapshotWritesEnabled: orchestrator.config.objectiveStateSnapshotWritesEnabled,
|
|
29044
|
+
sessionKey,
|
|
29045
|
+
recordedAt: eventTimestamp,
|
|
29046
|
+
messages
|
|
29047
|
+
});
|
|
29048
|
+
} catch (error) {
|
|
29049
|
+
log.debug(`agent_end objective-state writer skipped due to error: ${error}`);
|
|
29050
|
+
}
|
|
28724
29051
|
for (const msg of lastTurn) {
|
|
28725
29052
|
const rawRole = typeof msg.role === "string" ? msg.role : "";
|
|
28726
29053
|
if (rawRole !== "user" && rawRole !== "assistant") {
|
|
28727
29054
|
continue;
|
|
28728
29055
|
}
|
|
28729
29056
|
const role = rawRole;
|
|
28730
|
-
const content =
|
|
29057
|
+
const content = extractTextContent2(msg);
|
|
28731
29058
|
if (content.length < 10) continue;
|
|
28732
29059
|
const cleaned = role === "user" ? cleanUserMessage(content) : content;
|
|
28733
29060
|
if (orchestrator.config.transcriptEnabled) {
|
|
28734
29061
|
await orchestrator.transcript.append({
|
|
28735
|
-
timestamp:
|
|
29062
|
+
timestamp: eventTimestamp,
|
|
28736
29063
|
role,
|
|
28737
29064
|
content: cleaned,
|
|
28738
29065
|
sessionKey,
|
|
@@ -28920,7 +29247,7 @@ function extractLastTurn(messages) {
|
|
|
28920
29247
|
}
|
|
28921
29248
|
return lastUserIdx >= 0 ? messages.slice(lastUserIdx) : messages.slice(-2);
|
|
28922
29249
|
}
|
|
28923
|
-
function
|
|
29250
|
+
function extractTextContent2(msg) {
|
|
28924
29251
|
if (typeof msg.content === "string") return msg.content;
|
|
28925
29252
|
if (Array.isArray(msg.content)) {
|
|
28926
29253
|
return msg.content.filter(
|