@xopcai/xopc 0.0.25 → 0.0.27
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/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js +216 -0
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js.map +1 -0
- package/dist/gateway/static/root/assets/{apps-page-DbzO48lg.js → apps-page-CBBh_Ww8.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-DbzO48lg.js.map → apps-page-CBBh_Ww8.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js +9 -0
- package/dist/gateway/static/root/assets/{channels-settings-CeGoU9v8.js.map → channels-settings-DUKRPC7C.js.map} +1 -1
- package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js +2 -0
- package/dist/gateway/static/root/assets/{cron-page-DpEYUvxB.js.map → cron-page-S18t1yG-.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-utils-Cvv0F3pa.js → cron-utils-08gdQfl9.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-Cvv0F3pa.js.map → cron-utils-08gdQfl9.js.map} +1 -1
- package/dist/gateway/static/root/assets/dist-C1MrygQH.js +2 -0
- package/dist/gateway/static/root/assets/{dist-C41N3YrO.js.map → dist-C1MrygQH.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CkkYZjNP.js → extension-debug-page-DN3HKUGS.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-CkkYZjNP.js.map → extension-debug-page-DN3HKUGS.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BjUIPVNG.js → extension-page-CoFDHZtZ.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-BjUIPVNG.js.map → extension-page-CoFDHZtZ.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CwuFDOdk.js → extension-settings-page-BcPCu_Go.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-CwuFDOdk.js.map → extension-settings-page-BcPCu_Go.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-OT4cGzon.css +1 -0
- package/dist/gateway/static/root/assets/index-PfkB8N37.js +4734 -0
- package/dist/gateway/static/root/assets/index-PfkB8N37.js.map +1 -0
- package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +2 -0
- package/dist/gateway/static/root/assets/{logs-page-BtwGPuw2.js.map → logs-page-DoWe1GWy.js.map} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +2 -0
- package/dist/gateway/static/root/assets/{sessions-page-4rKFDn2k.js.map → sessions-page-2uOYwEwd.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-BmBDCEbY.js +3 -0
- package/dist/gateway/static/root/assets/{skills-page-_siDuHeF.js.map → skills-page-BmBDCEbY.js.map} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/memory/dreaming/config.d.ts +37 -9
- package/dist/src/agent/memory/dreaming/config.js +60 -18
- package/dist/src/agent/memory/dreaming/config.js.map +1 -1
- package/dist/src/agent/memory/dreaming/constants.d.ts +22 -1
- package/dist/src/agent/memory/dreaming/constants.js +26 -2
- package/dist/src/agent/memory/dreaming/constants.js.map +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +5 -6
- package/dist/src/agent/memory/dreaming/deep-promotion.js +90 -156
- package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -1
- package/dist/src/agent/memory/dreaming/events.d.ts +36 -0
- package/dist/src/agent/memory/dreaming/events.js +44 -0
- package/dist/src/agent/memory/dreaming/events.js.map +1 -0
- package/dist/src/agent/memory/dreaming/last-run.d.ts +80 -0
- package/dist/src/agent/memory/dreaming/last-run.js +98 -0
- package/dist/src/agent/memory/dreaming/last-run.js.map +1 -0
- package/dist/src/agent/memory/dreaming/light-sweep.d.ts +19 -0
- package/dist/src/agent/memory/dreaming/light-sweep.js +328 -0
- package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -0
- package/dist/src/agent/memory/dreaming/preview.d.ts +3 -1
- package/dist/src/agent/memory/dreaming/preview.js +11 -90
- package/dist/src/agent/memory/dreaming/preview.js.map +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +21 -0
- package/dist/src/agent/memory/dreaming/rem-patterns.js +286 -0
- package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -0
- package/dist/src/agent/memory/dreaming/short-term-store.d.ts +20 -0
- package/dist/src/agent/memory/dreaming/short-term-store.js +25 -15
- package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
- package/dist/src/agent/memory/dreaming/utils.d.ts +42 -0
- package/dist/src/agent/memory/dreaming/utils.js +141 -0
- package/dist/src/agent/memory/dreaming/utils.js.map +1 -0
- package/dist/src/agent/orchestration/agent-orchestrator.js +54 -12
- package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
- package/dist/src/agent/service.js +54 -28
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/config/schema.d.ts +54 -0
- package/dist/src/config/schema.js +34 -8
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +18 -0
- package/dist/src/gateway/hono/routes/dreaming.js +105 -15
- package/dist/src/gateway/hono/routes/dreaming.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +26 -1
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
- package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-C_bPhtBs.js +0 -216
- package/dist/gateway/static/root/assets/agents-C_bPhtBs.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-CeGoU9v8.js +0 -9
- package/dist/gateway/static/root/assets/cron-page-DpEYUvxB.js +0 -2
- package/dist/gateway/static/root/assets/dist-C41N3YrO.js +0 -2
- package/dist/gateway/static/root/assets/index-DwzwDCjW.js +0 -150
- package/dist/gateway/static/root/assets/index-DwzwDCjW.js.map +0 -1
- package/dist/gateway/static/root/assets/index-dhtHG1nU.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-BtwGPuw2.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-4rKFDn2k.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-iYLSxQYc.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-iYLSxQYc.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-_siDuHeF.js +0 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { DEFAULT_DEEP_CRON, DIVERSITY_WEIGHT, MS_PER_DAY, REINFORCEMENT_WEIGHT } from "./constants.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
//#region src/agent/memory/dreaming/utils.ts
|
|
6
|
+
/** Normalize a workspace-relative memory path: forward slashes, no odd ../ escapes at start. */
|
|
7
|
+
function normalizeMemoryPath(rel) {
|
|
8
|
+
if (typeof rel !== "string" || !rel.trim()) return "";
|
|
9
|
+
const s = rel.trim().replace(/\\/g, "/");
|
|
10
|
+
return path.posix.normalize(s).replace(/^(\.\/)+/, "").replace(/\/+/g, "/");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Stable id for a short-term store entry (path + line range).
|
|
14
|
+
* Format: `{normalizedPath}#{start}-{end}` (1-based, inclusive end).
|
|
15
|
+
*/
|
|
16
|
+
function buildEntryKey(parts) {
|
|
17
|
+
const p = normalizeMemoryPath(parts.path);
|
|
18
|
+
const a = Math.max(1, Math.floor(parts.startLine));
|
|
19
|
+
return `${p}#${a}-${Math.max(a, Math.floor(parts.endLine))}`;
|
|
20
|
+
}
|
|
21
|
+
/** YYYY-MM-DD in local time (for daily `memory/YYYY-MM-DD.md` names). */
|
|
22
|
+
function isoDay(d) {
|
|
23
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
24
|
+
}
|
|
25
|
+
function clamp01(value) {
|
|
26
|
+
if (!Number.isFinite(value)) return 0;
|
|
27
|
+
return Math.max(0, Math.min(1, value));
|
|
28
|
+
}
|
|
29
|
+
function toPositiveInt(value, fallback) {
|
|
30
|
+
const num = typeof value === "string" ? Number(value) : Number(value);
|
|
31
|
+
if (!Number.isFinite(num)) return fallback;
|
|
32
|
+
const floored = Math.floor(num);
|
|
33
|
+
return floored > 0 ? floored : fallback;
|
|
34
|
+
}
|
|
35
|
+
function toNonNegInt(value, fallback) {
|
|
36
|
+
const num = typeof value === "string" ? Number(value) : Number(value);
|
|
37
|
+
if (!Number.isFinite(num)) return fallback;
|
|
38
|
+
const floored = Math.floor(num);
|
|
39
|
+
return floored >= 0 ? floored : fallback;
|
|
40
|
+
}
|
|
41
|
+
function clampScore(value, fallback) {
|
|
42
|
+
if (!Number.isFinite(value)) return fallback;
|
|
43
|
+
return Math.max(0, Math.min(1, value));
|
|
44
|
+
}
|
|
45
|
+
function trimmedCron(value, fallback) {
|
|
46
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
47
|
+
}
|
|
48
|
+
/** Fills in deep-phase defaults; mirrors `resolveDreamingConfig` deep object when only partial overrides are given. */
|
|
49
|
+
function resolveDeepDefaults(overrides) {
|
|
50
|
+
return {
|
|
51
|
+
enabled: overrides?.enabled !== false,
|
|
52
|
+
cron: trimmedCron(overrides?.cron, DEFAULT_DEEP_CRON),
|
|
53
|
+
minScore: clampScore(Number(overrides?.minScore), .8),
|
|
54
|
+
minRecallCount: toPositiveInt(overrides?.minRecallCount, 3),
|
|
55
|
+
minUniqueQueries: toPositiveInt(overrides?.minUniqueQueries, 3),
|
|
56
|
+
limit: toNonNegInt(overrides?.limit, 10),
|
|
57
|
+
recencyHalfLifeDays: toPositiveInt(overrides?.recencyHalfLifeDays, 14),
|
|
58
|
+
maxAgeDays: toPositiveInt(overrides?.maxAgeDays, 30)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function computeCandidateScore(entry, nowMs, recencyHalfLifeDays) {
|
|
62
|
+
const recall = Math.max(0, entry.recallCount);
|
|
63
|
+
const avgScore = clamp01(recall > 0 ? entry.totalScore / recall : 0);
|
|
64
|
+
let recencyDecay = 1;
|
|
65
|
+
if (entry.lastRecalledAt) {
|
|
66
|
+
const last = Date.parse(entry.lastRecalledAt);
|
|
67
|
+
if (Number.isFinite(last)) recencyDecay = .5 ** (Math.max(0, (nowMs - last) / MS_PER_DAY) / Math.max(.1, recencyHalfLifeDays));
|
|
68
|
+
}
|
|
69
|
+
const logRecall = Math.log(1 + recall);
|
|
70
|
+
const reinforcement = REINFORCEMENT_WEIGHT * Math.min(logRecall, 3);
|
|
71
|
+
const qCount = entry.queryHashes?.length ?? 0;
|
|
72
|
+
const uniqueQueryTerm = Math.min(1, qCount / 6) * DIVERSITY_WEIGHT;
|
|
73
|
+
const signalDiversity = [
|
|
74
|
+
entry.dailyCount,
|
|
75
|
+
entry.groundedCount,
|
|
76
|
+
entry.lightHits,
|
|
77
|
+
entry.remHits
|
|
78
|
+
].filter((n) => n > 0).length / 4 * DIVERSITY_WEIGHT / 2;
|
|
79
|
+
return {
|
|
80
|
+
avgScore,
|
|
81
|
+
score: clamp01(avgScore * recencyDecay + reinforcement * .25 + uniqueQueryTerm + signalDiversity),
|
|
82
|
+
recencyDecay
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function compareCandidatesByScore(a, b) {
|
|
86
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
87
|
+
return a.key.localeCompare(b.key);
|
|
88
|
+
}
|
|
89
|
+
async function readFileLines(fullPath) {
|
|
90
|
+
try {
|
|
91
|
+
return (await fs.readFile(fullPath, "utf-8")).split(/\r?\n/);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err?.code === "ENOENT") return null;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** 1-based inclusive line range. Joins with newlines. */
|
|
98
|
+
function sliceRange(lines, startLine, endLine) {
|
|
99
|
+
const a = Math.max(1, Math.floor(startLine));
|
|
100
|
+
const b = Math.max(a, Math.floor(endLine));
|
|
101
|
+
return lines.slice(a - 1, b).join("\n").trimEnd();
|
|
102
|
+
}
|
|
103
|
+
function isExpiredEntry(lastRecalledAt, nowMs, maxAgeDays) {
|
|
104
|
+
if (!lastRecalledAt) return true;
|
|
105
|
+
const t = Date.parse(lastRecalledAt);
|
|
106
|
+
if (!Number.isFinite(t)) return true;
|
|
107
|
+
return (nowMs - t) / MS_PER_DAY > maxAgeDays;
|
|
108
|
+
}
|
|
109
|
+
/** Reject empty, tiny, or absurdly long snippets; blocks obvious “tool error” text. */
|
|
110
|
+
function isContaminatedSnippet(snippet) {
|
|
111
|
+
const s = snippet.trim();
|
|
112
|
+
if (s.length < 3) return true;
|
|
113
|
+
if (s.length > 12e3) return true;
|
|
114
|
+
if (!/\p{L}/u.test(s) && !/\d/.test(s)) return true;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function normalizeSnippetForHash(text) {
|
|
118
|
+
return text.toLowerCase().replace(/\s+/g, " ").replace(/['"`]/g, "").trim();
|
|
119
|
+
}
|
|
120
|
+
function snippetHash(text) {
|
|
121
|
+
const n = normalizeSnippetForHash(text);
|
|
122
|
+
return createHash("sha1").update(n, "utf8").digest("hex").slice(0, 20);
|
|
123
|
+
}
|
|
124
|
+
function extractPromotionMarkers(markdown) {
|
|
125
|
+
const keys = /* @__PURE__ */ new Set();
|
|
126
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
127
|
+
let m;
|
|
128
|
+
const re = /<!--\s*xopc-memory-promotion\s+key="([^"]*)"\s+hash="([^"]*)"\s*-->/g;
|
|
129
|
+
while ((m = re.exec(markdown)) !== null) {
|
|
130
|
+
if (m[1]) keys.add(m[1]);
|
|
131
|
+
if (m[2]) hashes.add(m[2]);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
keys,
|
|
135
|
+
hashes
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { buildEntryKey, clamp01, compareCandidatesByScore, computeCandidateScore, extractPromotionMarkers, isContaminatedSnippet, isExpiredEntry, isoDay, normalizeMemoryPath, normalizeSnippetForHash, readFileLines, resolveDeepDefaults, sliceRange, snippetHash };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/utils.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport type { DreamingDeepConfig } from './config.js';\nimport {\n DEFAULT_DEEP_CRON,\n DEFAULT_MAX_AGE_DAYS,\n DEFAULT_RECENCY_HALF_LIFE_DAYS,\n DIVERSITY_DIMENSION_COUNT,\n DIVERSITY_WEIGHT,\n MS_PER_DAY,\n REINFORCEMENT_WEIGHT,\n} from './constants.js';\nimport type { DreamingStoreEntry } from './short-term-store.js';\n\n// ── Path + keying ─────────────────────────────────────────────────────\n\n/** Normalize a workspace-relative memory path: forward slashes, no odd ../ escapes at start. */\nexport function normalizeMemoryPath(rel: string): string {\n if (typeof rel !== 'string' || !rel.trim()) return '';\n const s = rel.trim().replace(/\\\\/g, '/');\n return path.posix\n .normalize(s)\n .replace(/^(\\.\\/)+/, '')\n .replace(/\\/+/g, '/');\n}\n\n/**\n * Stable id for a short-term store entry (path + line range).\n * Format: `{normalizedPath}#{start}-{end}` (1-based, inclusive end).\n */\nexport function buildEntryKey(parts: { path: string; startLine: number; endLine: number }): string {\n const p = normalizeMemoryPath(parts.path);\n const a = Math.max(1, Math.floor(parts.startLine));\n const b = Math.max(a, Math.floor(parts.endLine));\n return `${p}#${a}-${b}`;\n}\n\n/** YYYY-MM-DD in local time (for daily `memory/YYYY-MM-DD.md` names). */\nexport function isoDay(d: Date): string {\n const y = d.getFullYear();\n const m = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${y}-${m}-${day}`;\n}\n\n// ── Numbers + deep config defaults ────────────────────────────────────\n\nexport function clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction toPositiveInt(value: unknown, fallback: number): number {\n const num = typeof value === 'string' ? Number(value) : Number(value);\n if (!Number.isFinite(num)) return fallback;\n const floored = Math.floor(num);\n return floored > 0 ? floored : fallback;\n}\n\nfunction toNonNegInt(value: unknown, fallback: number): number {\n const num = typeof value === 'string' ? Number(value) : Number(value);\n if (!Number.isFinite(num)) return fallback;\n const floored = Math.floor(num);\n return floored >= 0 ? floored : fallback;\n}\n\nfunction clampScore(value: number, fallback: number): number {\n if (!Number.isFinite(value)) return fallback;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction trimmedCron(value: unknown, fallback: string): string {\n return typeof value === 'string' && value.trim() ? value.trim() : fallback;\n}\n\n/** Fills in deep-phase defaults; mirrors `resolveDreamingConfig` deep object when only partial overrides are given. */\nexport function resolveDeepDefaults(overrides?: Partial<DreamingDeepConfig>): DreamingDeepConfig {\n return {\n enabled: overrides?.enabled !== false,\n cron: trimmedCron(overrides?.cron, DEFAULT_DEEP_CRON),\n minScore: clampScore(Number(overrides?.minScore), 0.8),\n minRecallCount: toPositiveInt(overrides?.minRecallCount, 3),\n minUniqueQueries: toPositiveInt(overrides?.minUniqueQueries, 3),\n limit: toNonNegInt(overrides?.limit, 10),\n recencyHalfLifeDays: toPositiveInt(overrides?.recencyHalfLifeDays, DEFAULT_RECENCY_HALF_LIFE_DAYS),\n maxAgeDays: toPositiveInt(overrides?.maxAgeDays, DEFAULT_MAX_AGE_DAYS),\n };\n}\n\n// ── Scoring (deep promotion) ─────────────────────────────────────────\n\nexport function computeCandidateScore(\n entry: DreamingStoreEntry,\n nowMs: number,\n recencyHalfLifeDays: number,\n): { avgScore: number; score: number; recencyDecay: number } {\n const recall = Math.max(0, entry.recallCount);\n const avgRaw = recall > 0 ? entry.totalScore / recall : 0;\n const avgScore = clamp01(avgRaw);\n\n let recencyDecay = 1;\n if (entry.lastRecalledAt) {\n const last = Date.parse(entry.lastRecalledAt);\n if (Number.isFinite(last)) {\n const days = Math.max(0, (nowMs - last) / MS_PER_DAY);\n const hl = Math.max(0.1, recencyHalfLifeDays);\n recencyDecay = 0.5 ** (days / hl);\n }\n }\n\n const logRecall = Math.log(1 + recall);\n const reinforcement = REINFORCEMENT_WEIGHT * Math.min(logRecall, 3);\n\n const qCount = entry.queryHashes?.length ?? 0;\n const uniqueQueryTerm = Math.min(1, qCount / 6) * DIVERSITY_WEIGHT;\n\n const dims = [entry.dailyCount, entry.groundedCount, entry.lightHits, entry.remHits].filter(\n (n) => n > 0,\n ).length;\n const signalDiversity = ((dims / DIVERSITY_DIMENSION_COUNT) * DIVERSITY_WEIGHT) / 2;\n\n const score = clamp01(avgScore * recencyDecay + reinforcement * 0.25 + uniqueQueryTerm + signalDiversity);\n\n return { avgScore, score, recencyDecay };\n}\n\nexport function compareCandidatesByScore(\n a: { score: number; key: string },\n b: { score: number; key: string },\n): number {\n if (b.score !== a.score) return b.score - a.score;\n return a.key.localeCompare(b.key);\n}\n\n// ── File slices + integrity ─────────────────────────────────────────\n\nexport async function readFileLines(fullPath: string): Promise<string[] | null> {\n try {\n const raw = await fs.readFile(fullPath, 'utf-8');\n return raw.split(/\\r?\\n/);\n } catch (err) {\n if ((err as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') return null;\n throw err;\n }\n}\n\n/** 1-based inclusive line range. Joins with newlines. */\nexport function sliceRange(lines: string[], startLine: number, endLine: number): string {\n const a = Math.max(1, Math.floor(startLine));\n const b = Math.max(a, Math.floor(endLine));\n const out = lines.slice(a - 1, b);\n return out.join('\\n').trimEnd();\n}\n\nexport function isExpiredEntry(\n lastRecalledAt: string | undefined,\n nowMs: number,\n maxAgeDays: number,\n): boolean {\n if (!lastRecalledAt) return true;\n const t = Date.parse(lastRecalledAt);\n if (!Number.isFinite(t)) return true;\n return (nowMs - t) / MS_PER_DAY > maxAgeDays;\n}\n\n/** Reject empty, tiny, or absurdly long snippets; blocks obvious “tool error” text. */\nexport function isContaminatedSnippet(snippet: string): boolean {\n const s = snippet.trim();\n if (s.length < 3) return true;\n if (s.length > 12_000) return true;\n if (!/\\p{L}/u.test(s) && !/\\d/.test(s)) return true;\n return false;\n}\n\nexport function normalizeSnippetForHash(text: string): string {\n return text\n .toLowerCase()\n .replace(/\\s+/g, ' ')\n .replace(/['\"`]/g, '')\n .trim();\n}\n\nexport function snippetHash(text: string): string {\n const n = normalizeSnippetForHash(text);\n return createHash('sha1').update(n, 'utf8').digest('hex').slice(0, 20);\n}\n\nexport function extractPromotionMarkers(markdown: string): { keys: Set<string>; hashes: Set<string> } {\n const keys = new Set<string>();\n const hashes = new Set<string>();\n let m: RegExpExecArray | null;\n const re = /<!--\\s*xopc-memory-promotion\\s+key=\"([^\"]*)\"\\s+hash=\"([^\"]*)\"\\s*-->/g;\n while ((m = re.exec(markdown)) !== null) {\n if (m[1]) keys.add(m[1]);\n if (m[2]) hashes.add(m[2]);\n }\n return { keys, hashes };\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,9 @@ import { runAgentTurnWithModelFallbacks } from "./run-agent-turn-with-fallbacks.
|
|
|
11
11
|
import { expandAtFileMentionsInPlainText } from "../context/expand-at-file-mentions.js";
|
|
12
12
|
import { resolveInboundImageContentParts } from "../image/inbound-image-handling.js";
|
|
13
13
|
import { runDreamingDeepPromotion } from "../memory/dreaming/deep-promotion.js";
|
|
14
|
+
import { appendDreamingEvent } from "../memory/dreaming/events.js";
|
|
15
|
+
import { runLightSweep } from "../memory/dreaming/light-sweep.js";
|
|
16
|
+
import { runRemPatterns } from "../memory/dreaming/rem-patterns.js";
|
|
14
17
|
//#region src/agent/orchestration/agent-orchestrator.ts
|
|
15
18
|
init_logger();
|
|
16
19
|
const log = createLogger("AgentOrchestrator");
|
|
@@ -56,19 +59,58 @@ var AgentOrchestrator = class {
|
|
|
56
59
|
const { sessionKey } = context;
|
|
57
60
|
log.debug({ sessionKey }, "Processing message through agent orchestrator");
|
|
58
61
|
await this.hydrateSessionWorkspaceFromStore?.(sessionKey);
|
|
59
|
-
if (typeof msg.content === "string" &&
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
if (typeof msg.content === "string" && (sessionKey.startsWith("cron:") || context.channel === "cron")) {
|
|
63
|
+
const content = msg.content;
|
|
64
|
+
if (content.includes("__xopc_memory_dreaming_sweep__") || content.includes("__xopc_memory_dreaming_light_sweep__") || content.includes("__xopc_memory_dreaming_rem_sweep__")) {
|
|
65
|
+
const workspaceDir = this.agentManager.getResolvedWorkspaceForSession(sessionKey);
|
|
66
|
+
const resolved = resolveDreamingConfig(this.getConfig?.());
|
|
67
|
+
const t0 = Date.now();
|
|
68
|
+
if (content.includes("__xopc_memory_dreaming_light_sweep__")) {
|
|
69
|
+
const result = await runLightSweep({
|
|
70
|
+
workspaceDir,
|
|
71
|
+
config: resolved.phases.light
|
|
72
|
+
});
|
|
73
|
+
await appendDreamingEvent(workspaceDir, {
|
|
74
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
phase: "light",
|
|
76
|
+
ok: result.ok,
|
|
77
|
+
reason: result.reason,
|
|
78
|
+
durationMs: Date.now() - t0,
|
|
79
|
+
scannedEntries: result.scannedEntries,
|
|
80
|
+
newSignals: result.newSignals,
|
|
81
|
+
deduped: result.deduped
|
|
82
|
+
});
|
|
83
|
+
} else if (content.includes("__xopc_memory_dreaming_rem_sweep__")) {
|
|
84
|
+
const result = await runRemPatterns({
|
|
85
|
+
workspaceDir,
|
|
86
|
+
config: resolved.phases.rem
|
|
87
|
+
});
|
|
88
|
+
await appendDreamingEvent(workspaceDir, {
|
|
89
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
90
|
+
phase: "rem",
|
|
91
|
+
ok: result.ok,
|
|
92
|
+
reason: result.reason,
|
|
93
|
+
durationMs: Date.now() - t0,
|
|
94
|
+
patternsDiscovered: result.patternsDiscovered,
|
|
95
|
+
entriesAnalyzed: result.entriesAnalyzed
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
const result = await runDreamingDeepPromotion({
|
|
99
|
+
workspaceDir,
|
|
100
|
+
config: resolved.phases.deep
|
|
101
|
+
});
|
|
102
|
+
await appendDreamingEvent(workspaceDir, {
|
|
103
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
phase: "deep",
|
|
105
|
+
ok: result.ok,
|
|
106
|
+
reason: result.reason,
|
|
107
|
+
durationMs: Date.now() - t0,
|
|
108
|
+
candidates: result.candidates,
|
|
109
|
+
applied: result.applied
|
|
110
|
+
});
|
|
69
111
|
}
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
72
114
|
}
|
|
73
115
|
const agent = this.agentManager.getOrCreateAgent(sessionKey);
|
|
74
116
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-orchestrator.js","names":[],"sources":["../../../../src/agent/orchestration/agent-orchestrator.ts"],"sourcesContent":["/**\n * Agent Orchestrator - Coordinates Agent execution flow\n *\n * Manages the complete agent execution pipeline from message processing\n * to response generation.\n */\n\nimport type { Agent, AgentMessage } from '@mariozechner/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport type { InboundMessage } from '../../infra/bus/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport { resolveEffectiveThinkingLevel } from '../../session/thinking-resolve.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { AgentEventHandler } from './agent-event-handler.js';\nimport type { FeedbackCoordinator } from '../feedback/feedback-coordinator.js';\nimport type { AgentManager } from '../agent-manager.js';\nimport { sanitizeMessages, cleanTrailingErrors } from '../memory/message-sanitizer.js';\nimport {\n tryApplySessionTranscriptHygiene,\n tryApplySessionTranscriptHygieneForPersistence,\n} from '../transcript/transcript-hygiene.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runAgentTurnWithModelFallbacks } from './run-agent-turn-with-fallbacks.js';\nimport {\n persistInboundAttachmentsToWorkspace,\n formatInboundFileTextBlock,\n} from '../../channels/attachments/inbound-persist.js';\nimport { expandAtFileMentionsInPlainText } from '../context/expand-at-file-mentions.js';\nimport { resolveInboundImageContentParts } from '../image/inbound-image-handling.js';\nimport { DREAMING_SWEEP_TOKEN } from '../memory/dreaming/constants.js';\nimport { resolveDreamingConfig } from '../memory/dreaming/config.js';\nimport { runDreamingDeepPromotion } from '../memory/dreaming/deep-promotion.js';\n\nconst log = createLogger('AgentOrchestrator');\n\nexport interface AgentOrchestratorConfig {\n agentManager: AgentManager;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n eventHandler: AgentEventHandler;\n feedbackCoordinator: FeedbackCoordinator;\n sessionConfigStore: SessionConfigStore;\n /** Load per-session workspace override and mkdir before creating the agent. */\n hydrateSessionWorkspaceFromStore?: (sessionKey: string) => Promise<void>;\n getThinkingDefault: () => ThinkLevel | undefined;\n /** Per-session default from merged `agents.list` / defaults (optional). */\n getThinkingDefaultForSession?: (sessionKey: string) => ThinkLevel | undefined;\n /** Default workspace root when no per-session resolver is set. */\n workspaceRoot: string;\n /** Per-agent workspace root for attachments (optional; defaults to `workspaceRoot`). */\n getWorkspaceRootForSession?: (sessionKey: string) => string;\n /** Agent home (`…/agents/<id>/`) for inbound/TTS files — keeps internal state out of the markdown workspace. */\n getAgentInternalStorageRootForSession?: (sessionKey: string) => string;\n /** Fire-and-forget after full session persist (e.g. LLM session title); not called from mid-turn snapshots. */\n enqueueAutoTitle?: (sessionKey: string) => void;\n /** For per-turn timeout via `agents.defaults.maxTaskDurationMs`. */\n getConfig?: () => Config | undefined;\n}\n\nexport class AgentOrchestrator {\n private agentManager: AgentManager;\n private sessionStore: SessionStore;\n private modelManager: ModelManager;\n private eventHandler: AgentEventHandler;\n private feedbackCoordinator: FeedbackCoordinator;\n private sessionConfigStore: SessionConfigStore;\n private hydrateSessionWorkspaceFromStore?: (sessionKey: string) => Promise<void>;\n private getThinkingDefault: () => ThinkLevel | undefined;\n private getThinkingDefaultForSession?: (sessionKey: string) => ThinkLevel | undefined;\n private workspaceRoot: string;\n private getWorkspaceRootForSession?: (sessionKey: string) => string;\n private getAgentInternalStorageRootForSession: (sessionKey: string) => string;\n private enqueueAutoTitle?: (sessionKey: string) => void;\n private getConfig?: () => Config | undefined;\n\n constructor(config: AgentOrchestratorConfig) {\n this.agentManager = config.agentManager;\n this.sessionStore = config.sessionStore;\n this.modelManager = config.modelManager;\n this.eventHandler = config.eventHandler;\n this.feedbackCoordinator = config.feedbackCoordinator;\n this.sessionConfigStore = config.sessionConfigStore;\n this.hydrateSessionWorkspaceFromStore = config.hydrateSessionWorkspaceFromStore;\n this.getThinkingDefault = config.getThinkingDefault;\n this.getThinkingDefaultForSession = config.getThinkingDefaultForSession;\n this.workspaceRoot = config.workspaceRoot;\n this.getWorkspaceRootForSession = config.getWorkspaceRootForSession;\n this.getAgentInternalStorageRootForSession =\n config.getAgentInternalStorageRootForSession ??\n ((sk) => this.getWorkspaceRootForSession?.(sk) ?? this.workspaceRoot);\n this.enqueueAutoTitle = config.enqueueAutoTitle;\n this.getConfig = config.getConfig;\n }\n\n private async hydrateSessionModelFromStore(sessionKey: string): Promise<void> {\n const cfg = await this.sessionConfigStore.get(sessionKey);\n if (cfg?.modelOverride) {\n await this.modelManager.switchModelForSession(sessionKey, cfg.modelOverride);\n }\n }\n\n /**\n * Process a message through the agent orchestration pipeline\n */\n async process(msg: InboundMessage, context: SessionContext): Promise<void> {\n const { sessionKey } = context;\n\n log.debug({ sessionKey }, 'Processing message through agent orchestrator');\n\n await this.hydrateSessionWorkspaceFromStore?.(sessionKey);\n\n // Dreaming: short-circuit cron-triggered sweep token into a maintenance run.\n // This avoids spending LLM tokens for scheduled memory consolidation.\n if (\n typeof msg.content === 'string' &&\n msg.content.includes(DREAMING_SWEEP_TOKEN) &&\n (sessionKey.startsWith('cron:') || context.channel === 'cron')\n ) {\n const workspaceDir = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n const resolved = resolveDreamingConfig(this.getConfig?.());\n await runDreamingDeepPromotion({\n workspaceDir,\n config: {\n enabled: resolved.deep.enabled,\n minScore: resolved.deep.minScore,\n minRecallCount: resolved.deep.minRecallCount,\n limit: resolved.deep.limit,\n },\n });\n return;\n }\n\n // Get or create agent for this session\n const agent = this.agentManager.getOrCreateAgent(sessionKey);\n\n try {\n await this.hydrateSessionModelFromStore(sessionKey);\n\n // 1. Load session history\n let messages = await this.sessionStore.load(sessionKey);\n\n // Clean any trailing errors from previous sessions (defensive)\n messages = cleanTrailingErrors(messages);\n\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n messages = tryApplySessionTranscriptHygiene(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene skipped (model resolve failed)');\n }\n\n agent.state.messages = messages;\n\n // 2. Apply model configuration for session\n await this.modelManager.applyModelForSession(agent, sessionKey);\n\n const thinkingDefault =\n this.getThinkingDefaultForSession?.(sessionKey) ?? this.getThinkingDefault();\n const thinkingLevel = await resolveEffectiveThinkingLevel(\n this.sessionConfigStore,\n sessionKey,\n null,\n thinkingDefault,\n );\n this.agentManager.setThinkingLevel(sessionKey, thinkingLevel);\n\n // 3. Persist inbound files (Telegram, etc.) under agent home, then build user message\n const storageRoot = this.getAgentInternalStorageRootForSession(sessionKey);\n const persistedAttachments = await persistInboundAttachmentsToWorkspace(\n storageRoot,\n sessionKey,\n msg.attachments,\n );\n const userMessage = await this.buildUserMessage(\n {\n ...msg,\n attachments: persistedAttachments ?? msg.attachments,\n },\n sessionKey,\n );\n const userPlainForMemory = extractAgentUserPlainText(userMessage);\n const userMessageForModel = await this.agentManager.applyMemoryPrefetchToUserMessage(\n userMessage,\n sessionKey,\n );\n\n // 4. Start task feedback\n this.feedbackCoordinator.startTask();\n\n // 5. Execute agent\n await this.executeAgent(agent, userMessageForModel, context);\n\n this.agentManager.afterAgentTurn(sessionKey, userPlainForMemory);\n this.agentManager.scheduleBackgroundReviewAfterUserTurn(sessionKey);\n\n // 6. Sanitize messages before saving (remove error messages, empty content)\n const rawMessages = agent.state.messages;\n const { messages: sanitizedMessages, removed } = sanitizeMessages(rawMessages);\n\n if (removed > 0) {\n log.info({ sessionKey, removed }, 'Removed problematic messages before saving');\n }\n\n // 7. Save session messages (transcript hygiene)\n await this.saveSessionSnapshot(sessionKey, sanitizedMessages);\n\n this.enqueueAutoTitle?.(sessionKey);\n\n // 8. End task feedback\n this.feedbackCoordinator.endTask();\n\n } catch (error) {\n log.error({ err: error, sessionKey }, 'Error in agent orchestration');\n this.feedbackCoordinator.endTask();\n throw error;\n }\n }\n\n /**\n * Transcript hygiene + persist. Expects messages already passed through {@link sanitizeMessages}.\n * Keeps thinking blocks on disk for UI; agent load path applies full hygiene including dropThinking.\n */\n private async saveSessionSnapshot(sessionKey: string, messages: AgentMessage[]): Promise<void> {\n let toPersist = messages;\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n toPersist = tryApplySessionTranscriptHygieneForPersistence(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on save skipped');\n }\n await this.sessionStore.save(sessionKey, toPersist);\n }\n\n /**\n * Execute the agent with a user message (primary model, then `agents.defaults.model.fallbacks` on failure).\n */\n private async executeAgent(\n agent: Agent,\n userMessage: AgentMessage,\n context: SessionContext\n ): Promise<void> {\n const sessionKey = context.sessionKey;\n await runAgentTurnWithModelFallbacks({\n agent,\n sessionKey,\n modelManager: this.modelManager,\n userMessage,\n log,\n getConfig: this.getConfig,\n beforeUserPrompt: () => this.agentManager.beginBackgroundReviewUserTurn(sessionKey),\n afterUserPrompt: async () => {\n try {\n const { messages: sanitizedTurn } = sanitizeMessages(agent.state.messages);\n await this.saveSessionSnapshot(sessionKey, sanitizedTurn);\n log.debug({ sessionKey }, 'User message saved immediately after prompt');\n } catch (err) {\n log.warn({ err, sessionKey }, 'Failed to save user message immediately');\n }\n },\n });\n }\n\n /**\n * Build an agent message from an inbound message\n */\n private async buildUserMessage(msg: InboundMessage, sessionKey: string): Promise<AgentMessage> {\n const storageRootAbs = this.getAgentInternalStorageRootForSession(sessionKey);\n let textBody = msg.content.trimStart().startsWith('/skill:')\n ? this.agentManager.expandSkillUserText(msg.content)\n : msg.content;\n\n if (/@file:/.test(textBody)) {\n const root = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n textBody = await expandAtFileMentionsInPlainText(textBody, root);\n }\n\n if (!msg.attachments || msg.attachments.length === 0) {\n return {\n role: 'user',\n content: textBody,\n timestamp: Date.now(),\n };\n }\n\n const modelRef = this.modelManager.getModelForSession(sessionKey);\n const cfg = this.getConfig?.();\n\n const messageContent: Array<\n { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n > = [];\n\n if (msg.content.trim()) {\n messageContent.push({ type: 'text', text: textBody });\n }\n\n const attachments = msg.attachments;\n let i = 0;\n while (i < attachments.length) {\n const att = attachments[i]!;\n const isImage =\n att.type === 'image' || att.type === 'photo' || Boolean(att.mimeType?.startsWith('image/'));\n\n if (isImage) {\n const group: Array<{ data: string; mimeType: string }> = [];\n while (i < attachments.length) {\n const a = attachments[i]!;\n const img =\n a.type === 'image' || a.type === 'photo' || Boolean(a.mimeType?.startsWith('image/'));\n if (!img) {\n break;\n }\n if (!a.data || a.data.length === 0) {\n log.warn({ type: a.type, name: a.name }, 'Empty image data, skipping');\n i += 1;\n continue;\n }\n group.push({ data: a.data, mimeType: a.mimeType || 'image/jpeg' });\n i += 1;\n }\n if (group.length > 0) {\n const parts = await resolveInboundImageContentParts({\n modelRef,\n cfg,\n userTextForContext: msg.content.trim() ? textBody : '',\n images: group,\n });\n messageContent.push(...parts);\n }\n } else {\n const fileBlock = formatInboundFileTextBlock(\n {\n type: att.type,\n mimeType: att.mimeType,\n name: att.name,\n size: att.size,\n workspaceRelativePath: att.workspaceRelativePath,\n },\n storageRootAbs,\n );\n messageContent.push({ type: 'text', text: fileBlock });\n i += 1;\n }\n }\n\n const hasText = messageContent.some((item) => item.type === 'text');\n const hasImage = messageContent.some((item) => item.type === 'image');\n if (hasImage && !hasText) {\n messageContent.unshift({ type: 'text', text: 'Please analyze the image(s) I sent.' });\n }\n\n if (messageContent.length === 0) {\n log.warn(\n { attachmentCount: msg.attachments.length },\n 'All attachments were skipped, falling back to text message',\n );\n return {\n role: 'user',\n content: textBody || '[Image attachment could not be processed]',\n timestamp: Date.now(),\n };\n }\n\n return {\n role: 'user',\n content: messageContent,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Get the current agent model ID\n */\n getCurrentModel(): string {\n return this.modelManager.getCurrentModel();\n }\n\n /**\n * Check if agent is currently processing for a session\n */\n isProcessing(sessionKey: string): boolean {\n const agent = this.agentManager.getAgent(sessionKey);\n if (!agent) {\n return false;\n }\n return agent.state.messages.length > 0;\n }\n\n /**\n * Abort current agent execution for a session\n */\n abort(sessionKey: string): void {\n const agent = this.agentManager.getAgent(sessionKey);\n if (agent) {\n agent.abort();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-orchestrator.js","names":[],"sources":["../../../../src/agent/orchestration/agent-orchestrator.ts"],"sourcesContent":["/**\n * Agent Orchestrator - Coordinates Agent execution flow\n *\n * Manages the complete agent execution pipeline from message processing\n * to response generation.\n */\n\nimport type { Agent, AgentMessage } from '@mariozechner/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport type { InboundMessage } from '../../infra/bus/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport { resolveEffectiveThinkingLevel } from '../../session/thinking-resolve.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { AgentEventHandler } from './agent-event-handler.js';\nimport type { FeedbackCoordinator } from '../feedback/feedback-coordinator.js';\nimport type { AgentManager } from '../agent-manager.js';\nimport { sanitizeMessages, cleanTrailingErrors } from '../memory/message-sanitizer.js';\nimport {\n tryApplySessionTranscriptHygiene,\n tryApplySessionTranscriptHygieneForPersistence,\n} from '../transcript/transcript-hygiene.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runAgentTurnWithModelFallbacks } from './run-agent-turn-with-fallbacks.js';\nimport {\n persistInboundAttachmentsToWorkspace,\n formatInboundFileTextBlock,\n} from '../../channels/attachments/inbound-persist.js';\nimport { expandAtFileMentionsInPlainText } from '../context/expand-at-file-mentions.js';\nimport { resolveInboundImageContentParts } from '../image/inbound-image-handling.js';\nimport {\n DREAMING_SWEEP_TOKEN,\n DREAMING_LIGHT_SWEEP_TOKEN,\n DREAMING_REM_SWEEP_TOKEN,\n} from '../memory/dreaming/constants.js';\nimport { resolveDreamingConfig } from '../memory/dreaming/config.js';\nimport { runDreamingDeepPromotion } from '../memory/dreaming/deep-promotion.js';\nimport { appendDreamingEvent, type DreamingEvent } from '../memory/dreaming/events.js';\nimport { runLightSweep } from '../memory/dreaming/light-sweep.js';\nimport { runRemPatterns } from '../memory/dreaming/rem-patterns.js';\n\nconst log = createLogger('AgentOrchestrator');\n\nexport interface AgentOrchestratorConfig {\n agentManager: AgentManager;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n eventHandler: AgentEventHandler;\n feedbackCoordinator: FeedbackCoordinator;\n sessionConfigStore: SessionConfigStore;\n /** Load per-session workspace override and mkdir before creating the agent. */\n hydrateSessionWorkspaceFromStore?: (sessionKey: string) => Promise<void>;\n getThinkingDefault: () => ThinkLevel | undefined;\n /** Per-session default from merged `agents.list` / defaults (optional). */\n getThinkingDefaultForSession?: (sessionKey: string) => ThinkLevel | undefined;\n /** Default workspace root when no per-session resolver is set. */\n workspaceRoot: string;\n /** Per-agent workspace root for attachments (optional; defaults to `workspaceRoot`). */\n getWorkspaceRootForSession?: (sessionKey: string) => string;\n /** Agent home (`…/agents/<id>/`) for inbound/TTS files — keeps internal state out of the markdown workspace. */\n getAgentInternalStorageRootForSession?: (sessionKey: string) => string;\n /** Fire-and-forget after full session persist (e.g. LLM session title); not called from mid-turn snapshots. */\n enqueueAutoTitle?: (sessionKey: string) => void;\n /** For per-turn timeout via `agents.defaults.maxTaskDurationMs`. */\n getConfig?: () => Config | undefined;\n}\n\nexport class AgentOrchestrator {\n private agentManager: AgentManager;\n private sessionStore: SessionStore;\n private modelManager: ModelManager;\n private eventHandler: AgentEventHandler;\n private feedbackCoordinator: FeedbackCoordinator;\n private sessionConfigStore: SessionConfigStore;\n private hydrateSessionWorkspaceFromStore?: (sessionKey: string) => Promise<void>;\n private getThinkingDefault: () => ThinkLevel | undefined;\n private getThinkingDefaultForSession?: (sessionKey: string) => ThinkLevel | undefined;\n private workspaceRoot: string;\n private getWorkspaceRootForSession?: (sessionKey: string) => string;\n private getAgentInternalStorageRootForSession: (sessionKey: string) => string;\n private enqueueAutoTitle?: (sessionKey: string) => void;\n private getConfig?: () => Config | undefined;\n\n constructor(config: AgentOrchestratorConfig) {\n this.agentManager = config.agentManager;\n this.sessionStore = config.sessionStore;\n this.modelManager = config.modelManager;\n this.eventHandler = config.eventHandler;\n this.feedbackCoordinator = config.feedbackCoordinator;\n this.sessionConfigStore = config.sessionConfigStore;\n this.hydrateSessionWorkspaceFromStore = config.hydrateSessionWorkspaceFromStore;\n this.getThinkingDefault = config.getThinkingDefault;\n this.getThinkingDefaultForSession = config.getThinkingDefaultForSession;\n this.workspaceRoot = config.workspaceRoot;\n this.getWorkspaceRootForSession = config.getWorkspaceRootForSession;\n this.getAgentInternalStorageRootForSession =\n config.getAgentInternalStorageRootForSession ??\n ((sk) => this.getWorkspaceRootForSession?.(sk) ?? this.workspaceRoot);\n this.enqueueAutoTitle = config.enqueueAutoTitle;\n this.getConfig = config.getConfig;\n }\n\n private async hydrateSessionModelFromStore(sessionKey: string): Promise<void> {\n const cfg = await this.sessionConfigStore.get(sessionKey);\n if (cfg?.modelOverride) {\n await this.modelManager.switchModelForSession(sessionKey, cfg.modelOverride);\n }\n }\n\n /**\n * Process a message through the agent orchestration pipeline\n */\n async process(msg: InboundMessage, context: SessionContext): Promise<void> {\n const { sessionKey } = context;\n\n log.debug({ sessionKey }, 'Processing message through agent orchestrator');\n\n await this.hydrateSessionWorkspaceFromStore?.(sessionKey);\n\n // Dreaming: short-circuit cron-triggered sweep tokens into maintenance runs.\n // This avoids spending LLM tokens for scheduled memory consolidation.\n if (\n typeof msg.content === 'string' &&\n (sessionKey.startsWith('cron:') || context.channel === 'cron')\n ) {\n const content = msg.content;\n const isDreamingSweep =\n content.includes(DREAMING_SWEEP_TOKEN) ||\n content.includes(DREAMING_LIGHT_SWEEP_TOKEN) ||\n content.includes(DREAMING_REM_SWEEP_TOKEN);\n\n if (isDreamingSweep) {\n const workspaceDir = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n const resolved = resolveDreamingConfig(this.getConfig?.());\n const t0 = Date.now();\n\n if (content.includes(DREAMING_LIGHT_SWEEP_TOKEN)) {\n const result = await runLightSweep({ workspaceDir, config: resolved.phases.light });\n const event: DreamingEvent = {\n timestamp: new Date().toISOString(), phase: 'light',\n ok: result.ok, reason: result.reason, durationMs: Date.now() - t0,\n scannedEntries: result.scannedEntries, newSignals: result.newSignals, deduped: result.deduped,\n };\n await appendDreamingEvent(workspaceDir, event);\n } else if (content.includes(DREAMING_REM_SWEEP_TOKEN)) {\n const result = await runRemPatterns({ workspaceDir, config: resolved.phases.rem });\n const event: DreamingEvent = {\n timestamp: new Date().toISOString(), phase: 'rem',\n ok: result.ok, reason: result.reason, durationMs: Date.now() - t0,\n patternsDiscovered: result.patternsDiscovered, entriesAnalyzed: result.entriesAnalyzed,\n };\n await appendDreamingEvent(workspaceDir, event);\n } else {\n const result = await runDreamingDeepPromotion({ workspaceDir, config: resolved.phases.deep });\n const event: DreamingEvent = {\n timestamp: new Date().toISOString(), phase: 'deep',\n ok: result.ok, reason: result.reason, durationMs: Date.now() - t0,\n candidates: result.candidates, applied: result.applied,\n };\n await appendDreamingEvent(workspaceDir, event);\n }\n return;\n }\n }\n\n // Get or create agent for this session\n const agent = this.agentManager.getOrCreateAgent(sessionKey);\n\n try {\n await this.hydrateSessionModelFromStore(sessionKey);\n\n // 1. Load session history\n let messages = await this.sessionStore.load(sessionKey);\n\n // Clean any trailing errors from previous sessions (defensive)\n messages = cleanTrailingErrors(messages);\n\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n messages = tryApplySessionTranscriptHygiene(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene skipped (model resolve failed)');\n }\n\n agent.state.messages = messages;\n\n // 2. Apply model configuration for session\n await this.modelManager.applyModelForSession(agent, sessionKey);\n\n const thinkingDefault =\n this.getThinkingDefaultForSession?.(sessionKey) ?? this.getThinkingDefault();\n const thinkingLevel = await resolveEffectiveThinkingLevel(\n this.sessionConfigStore,\n sessionKey,\n null,\n thinkingDefault,\n );\n this.agentManager.setThinkingLevel(sessionKey, thinkingLevel);\n\n // 3. Persist inbound files (Telegram, etc.) under agent home, then build user message\n const storageRoot = this.getAgentInternalStorageRootForSession(sessionKey);\n const persistedAttachments = await persistInboundAttachmentsToWorkspace(\n storageRoot,\n sessionKey,\n msg.attachments,\n );\n const userMessage = await this.buildUserMessage(\n {\n ...msg,\n attachments: persistedAttachments ?? msg.attachments,\n },\n sessionKey,\n );\n const userPlainForMemory = extractAgentUserPlainText(userMessage);\n const userMessageForModel = await this.agentManager.applyMemoryPrefetchToUserMessage(\n userMessage,\n sessionKey,\n );\n\n // 4. Start task feedback\n this.feedbackCoordinator.startTask();\n\n // 5. Execute agent\n await this.executeAgent(agent, userMessageForModel, context);\n\n this.agentManager.afterAgentTurn(sessionKey, userPlainForMemory);\n this.agentManager.scheduleBackgroundReviewAfterUserTurn(sessionKey);\n\n // 6. Sanitize messages before saving (remove error messages, empty content)\n const rawMessages = agent.state.messages;\n const { messages: sanitizedMessages, removed } = sanitizeMessages(rawMessages);\n\n if (removed > 0) {\n log.info({ sessionKey, removed }, 'Removed problematic messages before saving');\n }\n\n // 7. Save session messages (transcript hygiene)\n await this.saveSessionSnapshot(sessionKey, sanitizedMessages);\n\n this.enqueueAutoTitle?.(sessionKey);\n\n // 8. End task feedback\n this.feedbackCoordinator.endTask();\n\n } catch (error) {\n log.error({ err: error, sessionKey }, 'Error in agent orchestration');\n this.feedbackCoordinator.endTask();\n throw error;\n }\n }\n\n /**\n * Transcript hygiene + persist. Expects messages already passed through {@link sanitizeMessages}.\n * Keeps thinking blocks on disk for UI; agent load path applies full hygiene including dropThinking.\n */\n private async saveSessionSnapshot(sessionKey: string, messages: AgentMessage[]): Promise<void> {\n let toPersist = messages;\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n toPersist = tryApplySessionTranscriptHygieneForPersistence(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on save skipped');\n }\n await this.sessionStore.save(sessionKey, toPersist);\n }\n\n /**\n * Execute the agent with a user message (primary model, then `agents.defaults.model.fallbacks` on failure).\n */\n private async executeAgent(\n agent: Agent,\n userMessage: AgentMessage,\n context: SessionContext\n ): Promise<void> {\n const sessionKey = context.sessionKey;\n await runAgentTurnWithModelFallbacks({\n agent,\n sessionKey,\n modelManager: this.modelManager,\n userMessage,\n log,\n getConfig: this.getConfig,\n beforeUserPrompt: () => this.agentManager.beginBackgroundReviewUserTurn(sessionKey),\n afterUserPrompt: async () => {\n try {\n const { messages: sanitizedTurn } = sanitizeMessages(agent.state.messages);\n await this.saveSessionSnapshot(sessionKey, sanitizedTurn);\n log.debug({ sessionKey }, 'User message saved immediately after prompt');\n } catch (err) {\n log.warn({ err, sessionKey }, 'Failed to save user message immediately');\n }\n },\n });\n }\n\n /**\n * Build an agent message from an inbound message\n */\n private async buildUserMessage(msg: InboundMessage, sessionKey: string): Promise<AgentMessage> {\n const storageRootAbs = this.getAgentInternalStorageRootForSession(sessionKey);\n let textBody = msg.content.trimStart().startsWith('/skill:')\n ? this.agentManager.expandSkillUserText(msg.content)\n : msg.content;\n\n if (/@file:/.test(textBody)) {\n const root = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n textBody = await expandAtFileMentionsInPlainText(textBody, root);\n }\n\n if (!msg.attachments || msg.attachments.length === 0) {\n return {\n role: 'user',\n content: textBody,\n timestamp: Date.now(),\n };\n }\n\n const modelRef = this.modelManager.getModelForSession(sessionKey);\n const cfg = this.getConfig?.();\n\n const messageContent: Array<\n { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n > = [];\n\n if (msg.content.trim()) {\n messageContent.push({ type: 'text', text: textBody });\n }\n\n const attachments = msg.attachments;\n let i = 0;\n while (i < attachments.length) {\n const att = attachments[i]!;\n const isImage =\n att.type === 'image' || att.type === 'photo' || Boolean(att.mimeType?.startsWith('image/'));\n\n if (isImage) {\n const group: Array<{ data: string; mimeType: string }> = [];\n while (i < attachments.length) {\n const a = attachments[i]!;\n const img =\n a.type === 'image' || a.type === 'photo' || Boolean(a.mimeType?.startsWith('image/'));\n if (!img) {\n break;\n }\n if (!a.data || a.data.length === 0) {\n log.warn({ type: a.type, name: a.name }, 'Empty image data, skipping');\n i += 1;\n continue;\n }\n group.push({ data: a.data, mimeType: a.mimeType || 'image/jpeg' });\n i += 1;\n }\n if (group.length > 0) {\n const parts = await resolveInboundImageContentParts({\n modelRef,\n cfg,\n userTextForContext: msg.content.trim() ? textBody : '',\n images: group,\n });\n messageContent.push(...parts);\n }\n } else {\n const fileBlock = formatInboundFileTextBlock(\n {\n type: att.type,\n mimeType: att.mimeType,\n name: att.name,\n size: att.size,\n workspaceRelativePath: att.workspaceRelativePath,\n },\n storageRootAbs,\n );\n messageContent.push({ type: 'text', text: fileBlock });\n i += 1;\n }\n }\n\n const hasText = messageContent.some((item) => item.type === 'text');\n const hasImage = messageContent.some((item) => item.type === 'image');\n if (hasImage && !hasText) {\n messageContent.unshift({ type: 'text', text: 'Please analyze the image(s) I sent.' });\n }\n\n if (messageContent.length === 0) {\n log.warn(\n { attachmentCount: msg.attachments.length },\n 'All attachments were skipped, falling back to text message',\n );\n return {\n role: 'user',\n content: textBody || '[Image attachment could not be processed]',\n timestamp: Date.now(),\n };\n }\n\n return {\n role: 'user',\n content: messageContent,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Get the current agent model ID\n */\n getCurrentModel(): string {\n return this.modelManager.getCurrentModel();\n }\n\n /**\n * Check if agent is currently processing for a session\n */\n isProcessing(sessionKey: string): boolean {\n const agent = this.agentManager.getAgent(sessionKey);\n if (!agent) {\n return false;\n }\n return agent.state.messages.length > 0;\n }\n\n /**\n * Abort current agent execution for a session\n */\n abort(sessionKey: string): void {\n const agent = this.agentManager.getAgent(sessionKey);\n if (agent) {\n agent.abort();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { getDefaultModelSync, init_providers, resolveModel } from "../providers/
|
|
|
10
10
|
import { extractTextContent, loadBootstrapFiles } from "./context/workspace.js";
|
|
11
11
|
import { isTTSAvailable } from "../voice/tts/factory.js";
|
|
12
12
|
import { mergeTtsConfigFromAppConfig } from "../voice/tts/merge-config.js";
|
|
13
|
-
import { DREAMING_CRON_NAME, DREAMING_SWEEP_TOKEN } from "./memory/dreaming/constants.js";
|
|
13
|
+
import { DREAMING_CRON_NAME, DREAMING_LIGHT_CRON_NAME, DREAMING_LIGHT_SWEEP_TOKEN, DREAMING_REM_CRON_NAME, DREAMING_REM_SWEEP_TOKEN, DREAMING_SWEEP_TOKEN } from "./memory/dreaming/constants.js";
|
|
14
14
|
import { resolveDreamingConfig } from "./memory/dreaming/config.js";
|
|
15
15
|
import { speak } from "../voice/tts/speak-core.js";
|
|
16
16
|
import { getChannelOutputFormat, shouldUseTTS } from "../voice/tts/service.js";
|
|
@@ -453,37 +453,63 @@ var AgentService = class {
|
|
|
453
453
|
const cron = this.config.getCronService?.();
|
|
454
454
|
if (!cron) return;
|
|
455
455
|
const dreaming = resolveDreamingConfig(this.effectiveAppConfig());
|
|
456
|
-
const managed = (await cron.listJobs()).filter((job) => job.name
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
456
|
+
const managed = (await cron.listJobs()).filter((job) => job.name?.includes?.("[managed-by=xopc.memory.dreaming]") ?? false);
|
|
457
|
+
const phaseSpecs = [
|
|
458
|
+
{
|
|
459
|
+
cronName: DREAMING_LIGHT_CRON_NAME,
|
|
460
|
+
token: DREAMING_LIGHT_SWEEP_TOKEN,
|
|
461
|
+
schedule: dreaming.phases.light.cron,
|
|
462
|
+
phaseEnabled: dreaming.phases.light.enabled
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
cronName: DREAMING_CRON_NAME,
|
|
466
|
+
token: DREAMING_SWEEP_TOKEN,
|
|
467
|
+
schedule: dreaming.phases.deep.cron,
|
|
468
|
+
phaseEnabled: dreaming.phases.deep.enabled
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
cronName: DREAMING_REM_CRON_NAME,
|
|
472
|
+
token: DREAMING_REM_SWEEP_TOKEN,
|
|
473
|
+
schedule: dreaming.phases.rem.cron,
|
|
474
|
+
phaseEnabled: dreaming.phases.rem.enabled
|
|
475
|
+
}
|
|
476
|
+
];
|
|
477
|
+
if (!dreaming.enabled) {
|
|
469
478
|
for (const job of managed) await cron.removeJob(job.id).catch(() => {});
|
|
470
479
|
return;
|
|
471
480
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
481
|
+
for (const spec of phaseSpecs) {
|
|
482
|
+
const phaseJobs = managed.filter((job) => job.name === spec.cronName);
|
|
483
|
+
if (!spec.phaseEnabled) {
|
|
484
|
+
for (const job of phaseJobs) await cron.removeJob(job.id).catch(() => {});
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const desiredPayload = {
|
|
488
|
+
kind: "agentTurn",
|
|
489
|
+
message: spec.token
|
|
490
|
+
};
|
|
491
|
+
if (phaseJobs.length === 0) {
|
|
492
|
+
await cron.addJob(spec.schedule, {
|
|
493
|
+
name: spec.cronName,
|
|
494
|
+
timezone: dreaming.timezone,
|
|
495
|
+
sessionTarget: "isolated",
|
|
496
|
+
payload: desiredPayload,
|
|
497
|
+
enabled: true
|
|
498
|
+
});
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const primary = phaseJobs[0];
|
|
502
|
+
for (const dup of phaseJobs.slice(1)) await cron.removeJob(dup.id).catch(() => {});
|
|
503
|
+
const payloadMessage = primary.payload?.kind === "agentTurn" ? primary.payload.message : primary.payload?.text;
|
|
504
|
+
if (primary.schedule !== spec.schedule || (dreaming.timezone ?? null) !== (primary.timezone ?? null) || primary.sessionTarget !== "isolated" || payloadMessage !== spec.token || primary.enabled !== true || primary.name !== spec.cronName) await cron.updateJob(primary.id, {
|
|
505
|
+
schedule: spec.schedule,
|
|
506
|
+
timezone: dreaming.timezone ?? void 0,
|
|
507
|
+
sessionTarget: "isolated",
|
|
508
|
+
name: spec.cronName,
|
|
509
|
+
payload: desiredPayload,
|
|
510
|
+
enabled: true
|
|
511
|
+
});
|
|
475
512
|
}
|
|
476
|
-
const primary = managed[0];
|
|
477
|
-
for (const dup of managed.slice(1)) await cron.removeJob(dup.id).catch(() => {});
|
|
478
|
-
const payloadMessage = primary.payload?.kind === "agentTurn" ? primary.payload.message : primary.payload?.text;
|
|
479
|
-
if (primary.schedule !== dreaming.frequency || (dreaming.timezone ?? null) !== (primary.timezone ?? null) || primary.sessionTarget !== "isolated" || payloadMessage !== "__xopc_memory_dreaming_sweep__" || primary.enabled !== true || primary.name !== "Memory Dreaming - Deep Promotion") await cron.updateJob(primary.id, {
|
|
480
|
-
schedule: dreaming.frequency,
|
|
481
|
-
timezone: dreaming.timezone ?? void 0,
|
|
482
|
-
sessionTarget: "isolated",
|
|
483
|
-
name: DREAMING_CRON_NAME,
|
|
484
|
-
payload: desiredPayload,
|
|
485
|
-
enabled: true
|
|
486
|
-
});
|
|
487
513
|
}
|
|
488
514
|
/**
|
|
489
515
|
* Persist agent messages with the same sanitizer + transcript hygiene as AgentOrchestrator.
|