@xopcai/xopc 0.0.24 → 0.0.26

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.
Files changed (109) hide show
  1. package/dist/extensions/feishu/xopc.extension.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/agents-Clv9i1Kb.js +216 -0
  4. package/dist/gateway/static/root/assets/agents-Clv9i1Kb.js.map +1 -0
  5. package/dist/gateway/static/root/assets/{apps-page-tZz69XM3.js → apps-page-DqclV-PP.js} +2 -2
  6. package/dist/gateway/static/root/assets/{apps-page-tZz69XM3.js.map → apps-page-DqclV-PP.js.map} +1 -1
  7. package/dist/gateway/static/root/assets/{channels-settings-BAvk9-aK.js → channels-settings-CLyTYjrz.js} +3 -3
  8. package/dist/gateway/static/root/assets/{channels-settings-BAvk9-aK.js.map → channels-settings-CLyTYjrz.js.map} +1 -1
  9. package/dist/gateway/static/root/assets/cron-page-CU8lutMt.js +2 -0
  10. package/dist/gateway/static/root/assets/{cron-page-CANqvhK7.js.map → cron-page-CU8lutMt.js.map} +1 -1
  11. package/dist/gateway/static/root/assets/{cron-utils-DyOO6TdN.js → cron-utils-_UjiWax6.js} +2 -2
  12. package/dist/gateway/static/root/assets/{cron-utils-DyOO6TdN.js.map → cron-utils-_UjiWax6.js.map} +1 -1
  13. package/dist/gateway/static/root/assets/dist-Xqb4IGWC.js +2 -0
  14. package/dist/gateway/static/root/assets/{dist-Brod9LF3.js.map → dist-Xqb4IGWC.js.map} +1 -1
  15. package/dist/gateway/static/root/assets/{extension-debug-page-CDD7ozsC.js → extension-debug-page-CtTUkAmw.js} +2 -2
  16. package/dist/gateway/static/root/assets/{extension-debug-page-CDD7ozsC.js.map → extension-debug-page-CtTUkAmw.js.map} +1 -1
  17. package/dist/gateway/static/root/assets/{extension-page-UUFMjoWf.js → extension-page-C-aQU8qR.js} +2 -2
  18. package/dist/gateway/static/root/assets/{extension-page-UUFMjoWf.js.map → extension-page-C-aQU8qR.js.map} +1 -1
  19. package/dist/gateway/static/root/assets/{extension-settings-page-CP9JNc4m.js → extension-settings-page-b0y9aY-q.js} +2 -2
  20. package/dist/gateway/static/root/assets/extension-settings-page-b0y9aY-q.js.map +1 -0
  21. package/dist/gateway/static/root/assets/index-DhSFfSNN.css +1 -0
  22. package/dist/gateway/static/root/assets/index-Gr2HWo-G.js +4734 -0
  23. package/dist/gateway/static/root/assets/index-Gr2HWo-G.js.map +1 -0
  24. package/dist/gateway/static/root/assets/logs-page-DRI33XK4.js +2 -0
  25. package/dist/gateway/static/root/assets/{logs-page-Cr0eCGb4.js.map → logs-page-DRI33XK4.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/sessions-page-Cryg-36Z.js +2 -0
  27. package/dist/gateway/static/root/assets/{sessions-page-DwLHN5GJ.js.map → sessions-page-Cryg-36Z.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/settings-page-DFNKT9yg.js +2 -0
  29. package/dist/gateway/static/root/assets/settings-page-DFNKT9yg.js.map +1 -0
  30. package/dist/gateway/static/root/assets/skills-page-D4gfh0Ih.js +3 -0
  31. package/dist/gateway/static/root/assets/{skills-page-DgBYvH6B.js.map → skills-page-D4gfh0Ih.js.map} +1 -1
  32. package/dist/gateway/static/root/index.html +2 -2
  33. package/dist/package.js +1 -1
  34. package/dist/src/agent/memory/dreaming/config.d.ts +48 -0
  35. package/dist/src/agent/memory/dreaming/config.js +86 -0
  36. package/dist/src/agent/memory/dreaming/config.js.map +1 -0
  37. package/dist/src/agent/memory/dreaming/constants.d.ts +29 -0
  38. package/dist/src/agent/memory/dreaming/constants.js +38 -0
  39. package/dist/src/agent/memory/dreaming/constants.js.map +1 -0
  40. package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +21 -0
  41. package/dist/src/agent/memory/dreaming/deep-promotion.js +271 -0
  42. package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -0
  43. package/dist/src/agent/memory/dreaming/events.d.ts +36 -0
  44. package/dist/src/agent/memory/dreaming/events.js +44 -0
  45. package/dist/src/agent/memory/dreaming/events.js.map +1 -0
  46. package/dist/src/agent/memory/dreaming/last-run.d.ts +80 -0
  47. package/dist/src/agent/memory/dreaming/last-run.js +98 -0
  48. package/dist/src/agent/memory/dreaming/last-run.js.map +1 -0
  49. package/dist/src/agent/memory/dreaming/light-sweep.d.ts +19 -0
  50. package/dist/src/agent/memory/dreaming/light-sweep.js +328 -0
  51. package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -0
  52. package/dist/src/agent/memory/dreaming/preview.d.ts +28 -0
  53. package/dist/src/agent/memory/dreaming/preview.js +97 -0
  54. package/dist/src/agent/memory/dreaming/preview.js.map +1 -0
  55. package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +21 -0
  56. package/dist/src/agent/memory/dreaming/rem-patterns.js +286 -0
  57. package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -0
  58. package/dist/src/agent/memory/dreaming/short-term-store.d.ts +65 -0
  59. package/dist/src/agent/memory/dreaming/short-term-store.js +197 -0
  60. package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -0
  61. package/dist/src/agent/memory/dreaming/utils.d.ts +42 -0
  62. package/dist/src/agent/memory/dreaming/utils.js +141 -0
  63. package/dist/src/agent/memory/dreaming/utils.js.map +1 -0
  64. package/dist/src/agent/orchestration/agent-orchestrator.js +59 -0
  65. package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
  66. package/dist/src/agent/service.d.ts +6 -0
  67. package/dist/src/agent/service.js +78 -0
  68. package/dist/src/agent/service.js.map +1 -1
  69. package/dist/src/agent/tools/dreaming-tool.d.ts +7 -0
  70. package/dist/src/agent/tools/dreaming-tool.js +102 -0
  71. package/dist/src/agent/tools/dreaming-tool.js.map +1 -0
  72. package/dist/src/agent/tools/factory.js +5 -0
  73. package/dist/src/agent/tools/factory.js.map +1 -1
  74. package/dist/src/agent/tools/index.d.ts +1 -0
  75. package/dist/src/agent/tools/index.js +2 -1
  76. package/dist/src/agent/tools/memory-tool.js +9 -2
  77. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  78. package/dist/src/config/schema.d.ts +93 -0
  79. package/dist/src/config/schema.js +42 -1
  80. package/dist/src/config/schema.js.map +1 -1
  81. package/dist/src/gateway/hono/lib/config-payload.d.ts +31 -0
  82. package/dist/src/gateway/hono/routes/config.js +48 -0
  83. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  84. package/dist/src/gateway/hono/routes/dreaming.d.ts +3 -0
  85. package/dist/src/gateway/hono/routes/dreaming.js +288 -0
  86. package/dist/src/gateway/hono/routes/dreaming.js.map +1 -0
  87. package/dist/src/gateway/hono/routes/index.js +2 -0
  88. package/dist/src/gateway/hono/routes/index.js.map +1 -1
  89. package/dist/src/gateway/hono/routes/models.js +26 -1
  90. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  91. package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
  92. package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
  93. package/dist/src/gateway/lock.js +1 -1
  94. package/dist/src/gateway/service.js +7 -0
  95. package/dist/src/gateway/service.js.map +1 -1
  96. package/package.json +1 -1
  97. package/dist/gateway/static/root/assets/agents-CiZMJZRp.js +0 -216
  98. package/dist/gateway/static/root/assets/agents-CiZMJZRp.js.map +0 -1
  99. package/dist/gateway/static/root/assets/cron-page-CANqvhK7.js +0 -2
  100. package/dist/gateway/static/root/assets/dist-Brod9LF3.js +0 -2
  101. package/dist/gateway/static/root/assets/extension-settings-page-CP9JNc4m.js.map +0 -1
  102. package/dist/gateway/static/root/assets/index-BZvlG48D.js +0 -150
  103. package/dist/gateway/static/root/assets/index-BZvlG48D.js.map +0 -1
  104. package/dist/gateway/static/root/assets/index-DxkgyT8R.css +0 -1
  105. package/dist/gateway/static/root/assets/logs-page-Cr0eCGb4.js +0 -2
  106. package/dist/gateway/static/root/assets/sessions-page-DwLHN5GJ.js +0 -2
  107. package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js +0 -2
  108. package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js.map +0 -1
  109. package/dist/gateway/static/root/assets/skills-page-DgBYvH6B.js +0 -3
@@ -0,0 +1,197 @@
1
+ import { createLogger } from "../../../utils/logger/index.js";
2
+ import { init_logger } from "../../../utils/logger.js";
3
+ import { SHORT_TERM_PROMOTION_LOCK_RELATIVE, SHORT_TERM_RECALL_STORE_RELATIVE } from "./constants.js";
4
+ import { buildEntryKey, clamp01, isoDay, normalizeMemoryPath } from "./utils.js";
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ //#region src/agent/memory/dreaming/short-term-store.ts
9
+ init_logger();
10
+ const log = createLogger("Dreaming:Store");
11
+ function isDailyWorkspaceMemoryPath(rel) {
12
+ const p = normalizeMemoryPath(rel);
13
+ return /^memory\/\d{4}-\d{2}-\d{2}\.md$/i.test(p);
14
+ }
15
+ function hashQuery(query) {
16
+ return createHash("sha1").update(query.trim().toLowerCase()).digest("hex").slice(0, 12);
17
+ }
18
+ function mergeRecentDistinct(existing, nextValue, limit) {
19
+ const seen = /* @__PURE__ */ new Set();
20
+ const normalized = existing.filter((v) => typeof v === "string").map((v) => v.trim()).filter((v) => v.length > 0 && !seen.has(v) && (seen.add(v), true));
21
+ if (nextValue && !normalized.includes(nextValue)) normalized.push(nextValue);
22
+ return normalized.length <= limit ? normalized : normalized.slice(-limit);
23
+ }
24
+ function mergeQueryHashes(existing, nextHash) {
25
+ return mergeRecentDistinct(existing, nextHash, 32);
26
+ }
27
+ function emptyStore(nowIso) {
28
+ return {
29
+ version: 1,
30
+ updatedAt: nowIso,
31
+ entries: {}
32
+ };
33
+ }
34
+ async function ensureDreamDir(workspaceDir) {
35
+ const dir = path.join(workspaceDir, path.dirname(SHORT_TERM_RECALL_STORE_RELATIVE));
36
+ await fs.mkdir(dir, { recursive: true });
37
+ return dir;
38
+ }
39
+ async function readStore(workspaceDir, nowIso) {
40
+ const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);
41
+ try {
42
+ const raw = await fs.readFile(storePath, "utf-8");
43
+ const parsed = JSON.parse(raw);
44
+ if (!parsed || typeof parsed !== "object") return emptyStore(nowIso);
45
+ const rec = parsed;
46
+ if (rec.version !== 1 || !rec.entries || typeof rec.entries !== "object") return emptyStore(nowIso);
47
+ return {
48
+ version: 1,
49
+ updatedAt: typeof rec.updatedAt === "string" ? rec.updatedAt : nowIso,
50
+ entries: rec.entries
51
+ };
52
+ } catch (err) {
53
+ if (err?.code === "ENOENT") return emptyStore(nowIso);
54
+ log.warn({
55
+ err,
56
+ workspaceDir
57
+ }, "Failed to read dreaming store; resetting");
58
+ return emptyStore(nowIso);
59
+ }
60
+ }
61
+ async function writeStore(workspaceDir, store) {
62
+ await ensureDreamDir(workspaceDir);
63
+ const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);
64
+ const tmp = `${storePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
65
+ await fs.writeFile(tmp, `${JSON.stringify(store, null, 2)}\n`, "utf-8");
66
+ await fs.rename(tmp, storePath);
67
+ }
68
+ async function recordDreamingRecalls(params) {
69
+ const workspaceDir = params.workspaceDir.trim();
70
+ const query = params.query.trim();
71
+ if (!workspaceDir || !query) return {
72
+ recorded: 0,
73
+ skipped: params.matches.length,
74
+ storePath: SHORT_TERM_RECALL_STORE_RELATIVE
75
+ };
76
+ const now = params.now ?? /* @__PURE__ */ new Date();
77
+ const nowIso = now.toISOString();
78
+ const dayBucket = isoDay(now);
79
+ const qHash = hashQuery(query);
80
+ const store = await readStore(workspaceDir, nowIso);
81
+ let recorded = 0;
82
+ let skipped = 0;
83
+ for (const match of params.matches) {
84
+ const file = typeof match?.file === "string" ? match.file : "";
85
+ if (!file || !isDailyWorkspaceMemoryPath(file)) {
86
+ skipped += 1;
87
+ continue;
88
+ }
89
+ const lines = typeof match?.lines === "string" ? match.lines.trim() : "";
90
+ const lineNumbers = Array.isArray(match?.lineNumbers) ? match.lineNumbers : [];
91
+ const startLine = Math.max(1, Math.min(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
92
+ const endLine = Math.max(startLine, Math.max(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
93
+ if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) {
94
+ skipped += 1;
95
+ continue;
96
+ }
97
+ const score = clamp01(Number(match.score));
98
+ const key = buildEntryKey({
99
+ path: file,
100
+ startLine,
101
+ endLine
102
+ });
103
+ const existing = store.entries[key];
104
+ const snippet = lines.length > 0 ? lines.slice(0, 360) : file;
105
+ const next = existing ? {
106
+ ...existing,
107
+ snippet,
108
+ recallCount: Math.max(0, Math.floor(existing.recallCount + 1)),
109
+ dailyCount: existing.dailyCount ?? 0,
110
+ groundedCount: existing.groundedCount ?? 0,
111
+ lightHits: existing.lightHits ?? 0,
112
+ remHits: existing.remHits ?? 0,
113
+ phaseHitCount: existing.phaseHitCount ?? 0,
114
+ totalSignalCount: Math.max(0, (existing.totalSignalCount ?? existing.recallCount ?? 0) + 1),
115
+ totalScore: Math.max(0, existing.totalScore + score),
116
+ maxScore: Math.max(existing.maxScore, score),
117
+ queryHashes: mergeQueryHashes(existing.queryHashes ?? [], qHash),
118
+ recallDays: mergeRecentDistinct(existing.recallDays ?? [], dayBucket, 16),
119
+ lastRecalledAt: nowIso
120
+ } : {
121
+ key,
122
+ path: normalizeMemoryPath(file),
123
+ startLine,
124
+ endLine,
125
+ snippet,
126
+ recallCount: 1,
127
+ dailyCount: 0,
128
+ groundedCount: 0,
129
+ lightHits: 0,
130
+ remHits: 0,
131
+ phaseHitCount: 0,
132
+ totalSignalCount: 1,
133
+ totalScore: score,
134
+ maxScore: score,
135
+ queryHashes: [qHash],
136
+ recallDays: [dayBucket],
137
+ firstRecalledAt: nowIso,
138
+ lastRecalledAt: nowIso
139
+ };
140
+ store.entries[key] = next;
141
+ recorded += 1;
142
+ }
143
+ if (recorded > 0) {
144
+ store.updatedAt = nowIso;
145
+ await writeStore(workspaceDir, store);
146
+ }
147
+ return {
148
+ recorded,
149
+ skipped,
150
+ storePath: SHORT_TERM_RECALL_STORE_RELATIVE
151
+ };
152
+ }
153
+ async function withDreamingPromotionLock(workspaceDir, task) {
154
+ const lockPath = path.join(workspaceDir, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
155
+ await ensureDreamDir(workspaceDir);
156
+ const startedAt = Date.now();
157
+ const timeoutMs = 1e4;
158
+ const retryDelayMs = 50;
159
+ while (true) try {
160
+ const handle = await fs.open(lockPath, "wx");
161
+ await handle.writeFile(`${process.pid}:${Date.now()}\n`, "utf-8").catch(() => void 0);
162
+ try {
163
+ return await task();
164
+ } finally {
165
+ await handle.close().catch(() => void 0);
166
+ await fs.unlink(lockPath).catch(() => void 0);
167
+ }
168
+ } catch (err) {
169
+ if (err?.code !== "EEXIST") throw err;
170
+ if (Date.now() - startedAt >= timeoutMs) throw new Error(`Timed out waiting for dreaming promotion lock at ${lockPath}`);
171
+ await new Promise((r) => setTimeout(r, retryDelayMs));
172
+ }
173
+ }
174
+ async function loadDreamingStore(params) {
175
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
176
+ return {
177
+ store: await readStore(params.workspaceDir, nowIso),
178
+ storePath: SHORT_TERM_RECALL_STORE_RELATIVE
179
+ };
180
+ }
181
+ async function saveDreamingStore(params) {
182
+ await writeStore(params.workspaceDir, params.store);
183
+ }
184
+ /**
185
+ * Increment a phase-specific signal counter on an existing store entry.
186
+ * Also bumps `phaseHitCount` and `totalSignalCount`.
187
+ * Returns `true` if the entry existed and was updated.
188
+ */
189
+ function bumpEntryPhaseSignal(entry, field, increment = 1) {
190
+ entry[field] = (entry[field] ?? 0) + increment;
191
+ entry.phaseHitCount = (entry.phaseHitCount ?? 0) + increment;
192
+ entry.totalSignalCount = (entry.totalSignalCount ?? 0) + increment;
193
+ }
194
+ //#endregion
195
+ export { bumpEntryPhaseSignal, loadDreamingStore, recordDreamingRecalls, saveDreamingStore, withDreamingPromotionLock };
196
+
197
+ //# sourceMappingURL=short-term-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"short-term-store.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/short-term-store.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash, randomUUID } from 'node:crypto';\n\nimport type { MemorySearchOptions } from '../../prompt/memory/index.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport {\n SHORT_TERM_PROMOTION_LOCK_RELATIVE,\n SHORT_TERM_RECALL_STORE_RELATIVE,\n} from './constants.js';\nimport { buildEntryKey, clamp01, isoDay, normalizeMemoryPath } from './utils.js';\n\nconst log = createLogger('Dreaming:Store');\n\nexport type DreamingStoreEntry = {\n key: string;\n path: string; // workspace-relative: memory/YYYY-MM-DD.md\n startLine: number;\n endLine: number;\n snippet: string;\n\n // ── Signal dimensions ────────────────────────────────────────────────\n /** Number of times this snippet was returned by a memory recall query. */\n recallCount: number;\n /** Number of times recorded from daily log scanning (light sweep). */\n dailyCount: number;\n /** Number of times replayed from grounded context (agent-initiated). */\n groundedCount: number;\n /** Number of times the light phase touched this entry. */\n lightHits: number;\n /** Number of times the REM phase touched this entry. */\n remHits: number;\n /** Cross-phase hit count (light + deep + rem combined touches). */\n phaseHitCount: number;\n /** Weighted aggregate of all signal dimensions. */\n totalSignalCount: number;\n\n // ── Score tracking ───────────────────────────────────────────────────\n totalScore: number;\n maxScore: number;\n queryHashes: string[];\n recallDays: string[];\n\n // ── Timestamps ───────────────────────────────────────────────────────\n firstRecalledAt: string;\n lastRecalledAt: string;\n promotedAt?: string;\n};\n\nexport type DreamingStore = {\n version: 1;\n updatedAt: string;\n entries: Record<string, DreamingStoreEntry>;\n};\n\ntype MemoryMatch = Awaited<ReturnType<typeof import('../../prompt/memory/index.js').memorySearch>>[number];\n\nfunction isDailyWorkspaceMemoryPath(rel: string): boolean {\n const p = normalizeMemoryPath(rel);\n return /^memory\\/\\d{4}-\\d{2}-\\d{2}\\.md$/i.test(p);\n}\n\nfunction hashQuery(query: string): string {\n return createHash('sha1').update(query.trim().toLowerCase()).digest('hex').slice(0, 12);\n}\n\nfunction mergeRecentDistinct(existing: string[], nextValue: string, limit: number): string[] {\n const seen = new Set<string>();\n const normalized = existing\n .filter((v): v is string => typeof v === 'string')\n .map((v) => v.trim())\n .filter((v) => v.length > 0 && !seen.has(v) && (seen.add(v), true));\n if (nextValue && !normalized.includes(nextValue)) {\n normalized.push(nextValue);\n }\n return normalized.length <= limit ? normalized : normalized.slice(-limit);\n}\n\nfunction mergeQueryHashes(existing: string[], nextHash: string): string[] {\n const out = mergeRecentDistinct(existing, nextHash, 32);\n return out;\n}\n\nfunction emptyStore(nowIso: string): DreamingStore {\n return { version: 1, updatedAt: nowIso, entries: {} };\n}\n\nasync function ensureDreamDir(workspaceDir: string): Promise<string> {\n const dir = path.join(workspaceDir, path.dirname(SHORT_TERM_RECALL_STORE_RELATIVE));\n await fs.mkdir(dir, { recursive: true });\n return dir;\n}\n\nasync function readStore(workspaceDir: string, nowIso: string): Promise<DreamingStore> {\n const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);\n try {\n const raw = await fs.readFile(storePath, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== 'object') return emptyStore(nowIso);\n const rec = parsed as Partial<DreamingStore>;\n if (rec.version !== 1 || !rec.entries || typeof rec.entries !== 'object') {\n return emptyStore(nowIso);\n }\n return {\n version: 1,\n updatedAt: typeof rec.updatedAt === 'string' ? rec.updatedAt : nowIso,\n entries: rec.entries as Record<string, DreamingStoreEntry>,\n };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'ENOENT') return emptyStore(nowIso);\n log.warn({ err, workspaceDir }, 'Failed to read dreaming store; resetting');\n return emptyStore(nowIso);\n }\n}\n\nasync function writeStore(workspaceDir: string, store: DreamingStore): Promise<void> {\n await ensureDreamDir(workspaceDir);\n const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);\n const tmp = `${storePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;\n await fs.writeFile(tmp, `${JSON.stringify(store, null, 2)}\\n`, 'utf-8');\n await fs.rename(tmp, storePath);\n}\n\nexport async function recordDreamingRecalls(params: {\n workspaceDir: string;\n query: string;\n matches: MemoryMatch[];\n options?: MemorySearchOptions;\n now?: Date;\n}): Promise<{ recorded: number; skipped: number; storePath: string }> {\n const workspaceDir = params.workspaceDir.trim();\n const query = params.query.trim();\n if (!workspaceDir || !query) {\n return { recorded: 0, skipped: params.matches.length, storePath: SHORT_TERM_RECALL_STORE_RELATIVE };\n }\n const now = params.now ?? new Date();\n const nowIso = now.toISOString();\n const dayBucket = isoDay(now);\n const qHash = hashQuery(query);\n\n const store = await readStore(workspaceDir, nowIso);\n\n let recorded = 0;\n let skipped = 0;\n for (const match of params.matches) {\n const file = typeof match?.file === \"string\" ? match.file : \"\";\n if (!file || !isDailyWorkspaceMemoryPath(file)) {\n skipped += 1;\n continue;\n }\n const lines = typeof match?.lines === \"string\" ? match.lines.trim() : \"\";\n const lineNumbers = Array.isArray(match?.lineNumbers) ? match.lineNumbers : [];\n const startLine = Math.max(1, Math.min(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));\n const endLine = Math.max(startLine, Math.max(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));\n if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) {\n skipped += 1;\n continue;\n }\n const score = clamp01(Number(match.score));\n const key = buildEntryKey({ path: file, startLine, endLine });\n const existing = store.entries[key];\n const snippet = lines.length > 0 ? lines.slice(0, 360) : file;\n\n const next: DreamingStoreEntry = existing\n ? {\n ...existing,\n snippet,\n recallCount: Math.max(0, Math.floor(existing.recallCount + 1)),\n dailyCount: existing.dailyCount ?? 0,\n groundedCount: existing.groundedCount ?? 0,\n lightHits: existing.lightHits ?? 0,\n remHits: existing.remHits ?? 0,\n phaseHitCount: existing.phaseHitCount ?? 0,\n totalSignalCount: Math.max(0, (existing.totalSignalCount ?? existing.recallCount ?? 0) + 1),\n totalScore: Math.max(0, existing.totalScore + score),\n maxScore: Math.max(existing.maxScore, score),\n queryHashes: mergeQueryHashes(existing.queryHashes ?? [], qHash),\n recallDays: mergeRecentDistinct(existing.recallDays ?? [], dayBucket, 16),\n lastRecalledAt: nowIso,\n }\n : {\n key,\n path: normalizeMemoryPath(file),\n startLine,\n endLine,\n snippet,\n recallCount: 1,\n dailyCount: 0,\n groundedCount: 0,\n lightHits: 0,\n remHits: 0,\n phaseHitCount: 0,\n totalSignalCount: 1,\n totalScore: score,\n maxScore: score,\n queryHashes: [qHash],\n recallDays: [dayBucket],\n firstRecalledAt: nowIso,\n lastRecalledAt: nowIso,\n };\n\n store.entries[key] = next;\n recorded += 1;\n }\n\n if (recorded > 0) {\n store.updatedAt = nowIso;\n await writeStore(workspaceDir, store);\n }\n\n return {\n recorded,\n skipped,\n storePath: SHORT_TERM_RECALL_STORE_RELATIVE,\n };\n}\n\nexport async function withDreamingPromotionLock<T>(\n workspaceDir: string,\n task: () => Promise<T>,\n): Promise<T> {\n const lockPath = path.join(workspaceDir, SHORT_TERM_PROMOTION_LOCK_RELATIVE);\n await ensureDreamDir(workspaceDir);\n\n const startedAt = Date.now();\n const timeoutMs = 10_000;\n const retryDelayMs = 50;\n\n while (true) {\n try {\n const handle = await fs.open(lockPath, 'wx');\n await handle.writeFile(`${process.pid}:${Date.now()}\\n`, 'utf-8').catch(() => undefined);\n try {\n return await task();\n } finally {\n await handle.close().catch(() => undefined);\n await fs.unlink(lockPath).catch(() => undefined);\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code !== 'EEXIST') {\n throw err;\n }\n if (Date.now() - startedAt >= timeoutMs) {\n throw new Error(`Timed out waiting for dreaming promotion lock at ${lockPath}`);\n }\n await new Promise((r) => setTimeout(r, retryDelayMs));\n }\n }\n}\n\nexport async function loadDreamingStore(params: {\n workspaceDir: string;\n}): Promise<{ store: DreamingStore; storePath: string }> {\n const nowIso = new Date().toISOString();\n const store = await readStore(params.workspaceDir, nowIso);\n return { store, storePath: SHORT_TERM_RECALL_STORE_RELATIVE };\n}\n\nexport async function saveDreamingStore(params: {\n workspaceDir: string;\n store: DreamingStore;\n}): Promise<void> {\n await writeStore(params.workspaceDir, params.store);\n}\n\n// ── Phase-level signal helpers ─────────────────────────────────────────\n\ntype PhaseSignalField = 'dailyCount' | 'groundedCount' | 'lightHits' | 'remHits';\n\n/**\n * Increment a phase-specific signal counter on an existing store entry.\n * Also bumps `phaseHitCount` and `totalSignalCount`.\n * Returns `true` if the entry existed and was updated.\n */\nexport function bumpEntryPhaseSignal(\n entry: DreamingStoreEntry,\n field: PhaseSignalField,\n increment = 1,\n): void {\n entry[field] = (entry[field] ?? 0) + increment;\n entry.phaseHitCount = (entry.phaseHitCount ?? 0) + increment;\n entry.totalSignalCount = (entry.totalSignalCount ?? 0) + increment;\n}\n\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { DreamingDeepConfig } from './config.js';
2
+ import type { DreamingStoreEntry } from './short-term-store.js';
3
+ /** Normalize a workspace-relative memory path: forward slashes, no odd ../ escapes at start. */
4
+ export declare function normalizeMemoryPath(rel: string): string;
5
+ /**
6
+ * Stable id for a short-term store entry (path + line range).
7
+ * Format: `{normalizedPath}#{start}-{end}` (1-based, inclusive end).
8
+ */
9
+ export declare function buildEntryKey(parts: {
10
+ path: string;
11
+ startLine: number;
12
+ endLine: number;
13
+ }): string;
14
+ /** YYYY-MM-DD in local time (for daily `memory/YYYY-MM-DD.md` names). */
15
+ export declare function isoDay(d: Date): string;
16
+ export declare function clamp01(value: number): number;
17
+ /** Fills in deep-phase defaults; mirrors `resolveDreamingConfig` deep object when only partial overrides are given. */
18
+ export declare function resolveDeepDefaults(overrides?: Partial<DreamingDeepConfig>): DreamingDeepConfig;
19
+ export declare function computeCandidateScore(entry: DreamingStoreEntry, nowMs: number, recencyHalfLifeDays: number): {
20
+ avgScore: number;
21
+ score: number;
22
+ recencyDecay: number;
23
+ };
24
+ export declare function compareCandidatesByScore(a: {
25
+ score: number;
26
+ key: string;
27
+ }, b: {
28
+ score: number;
29
+ key: string;
30
+ }): number;
31
+ export declare function readFileLines(fullPath: string): Promise<string[] | null>;
32
+ /** 1-based inclusive line range. Joins with newlines. */
33
+ export declare function sliceRange(lines: string[], startLine: number, endLine: number): string;
34
+ export declare function isExpiredEntry(lastRecalledAt: string | undefined, nowMs: number, maxAgeDays: number): boolean;
35
+ /** Reject empty, tiny, or absurdly long snippets; blocks obvious “tool error” text. */
36
+ export declare function isContaminatedSnippet(snippet: string): boolean;
37
+ export declare function normalizeSnippetForHash(text: string): string;
38
+ export declare function snippetHash(text: string): string;
39
+ export declare function extractPromotionMarkers(markdown: string): {
40
+ keys: Set<string>;
41
+ hashes: Set<string>;
42
+ };
@@ -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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"}
@@ -1,5 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import "../memory/dreaming/constants.js";
4
+ import { resolveDreamingConfig } from "../memory/dreaming/config.js";
3
5
  import { extractAgentUserPlainText } from "../memory/user-message-text.js";
4
6
  import { formatInboundFileTextBlock, persistInboundAttachmentsToWorkspace } from "../../channels/attachments/inbound-persist.js";
5
7
  import { cleanTrailingErrors, sanitizeMessages } from "../memory/message-sanitizer.js";
@@ -8,6 +10,10 @@ import { tryApplySessionTranscriptHygiene, tryApplySessionTranscriptHygieneForPe
8
10
  import { runAgentTurnWithModelFallbacks } from "./run-agent-turn-with-fallbacks.js";
9
11
  import { expandAtFileMentionsInPlainText } from "../context/expand-at-file-mentions.js";
10
12
  import { resolveInboundImageContentParts } from "../image/inbound-image-handling.js";
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";
11
17
  //#region src/agent/orchestration/agent-orchestrator.ts
12
18
  init_logger();
13
19
  const log = createLogger("AgentOrchestrator");
@@ -53,6 +59,59 @@ var AgentOrchestrator = class {
53
59
  const { sessionKey } = context;
54
60
  log.debug({ sessionKey }, "Processing message through agent orchestrator");
55
61
  await this.hydrateSessionWorkspaceFromStore?.(sessionKey);
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
+ });
111
+ }
112
+ return;
113
+ }
114
+ }
56
115
  const agent = this.agentManager.getOrCreateAgent(sessionKey);
57
116
  try {
58
117
  await this.hydrateSessionModelFromStore(sessionKey);
@@ -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';\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 // 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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"}
@@ -76,6 +76,12 @@ export declare class AgentService {
76
76
  clearStreamHandle(): void;
77
77
  start(): Promise<void>;
78
78
  stop(): Promise<void>;
79
+ /**
80
+ * Reconcile managed Dreaming cron job against the current effective config.
81
+ * Safe to call after config saves to apply changes without restarting the process.
82
+ */
83
+ reconcileDreamingNow(): Promise<void>;
84
+ private reconcileDreamingCronJob;
79
85
  /**
80
86
  * Persist agent messages with the same sanitizer + transcript hygiene as AgentOrchestrator.
81
87
  * Uses persistence hygiene so `thinking` blocks remain on disk for the web UI (LLM load path still drops them).