@hilbras/omninode 0.7.0 → 0.8.0

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/index.js CHANGED
@@ -160,7 +160,11 @@ var projectConfigSchema = z.object({
160
160
  agents: z.array(agentConfigSchema).default([]),
161
161
  roles: z.array(roleConfigSchema).default([]),
162
162
  pipelines: z.array(pipelineConfigSchema).default([]),
163
- memory: z.object({ provider: z.string().min(1) }).strict().optional()
163
+ memory: z.object({
164
+ provider: z.string().min(1).default("local"),
165
+ base_url: z.string().url().optional(),
166
+ api_key_env_var: z.string().min(1).optional()
167
+ }).strict().optional()
164
168
  }).strict();
165
169
  var appConfigSchema = z.object({
166
170
  project: projectConfigSchema,
@@ -230,7 +234,13 @@ function toAppConfig(yaml) {
230
234
  agents: yaml.project.agents.map(toAgentConfig),
231
235
  roles: yaml.project.roles.map(toRoleDefinition),
232
236
  pipelines: yaml.project.pipelines.map(toPipelineDefinition),
233
- ...yaml.project.memory !== void 0 ? { memory: yaml.project.memory } : {}
237
+ ...yaml.project.memory !== void 0 ? {
238
+ memory: {
239
+ provider: yaml.project.memory.provider,
240
+ baseUrl: yaml.project.memory.base_url,
241
+ apiKeyEnvVar: yaml.project.memory.api_key_env_var
242
+ }
243
+ } : {}
234
244
  },
235
245
  ...yaml.logging !== void 0 ? { logging: yaml.logging } : {}
236
246
  };
@@ -1079,13 +1089,24 @@ var TaskEngine = class {
1079
1089
  const role = task.role ? this.options.roles.get(task.role) : void 0;
1080
1090
  const running = await this.transition(task, "running");
1081
1091
  this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
1092
+ let background = running.context?.background;
1093
+ if (this.options.memory) {
1094
+ const memoryContext = await this.options.memory.gatherContext({
1095
+ objective: running.objective,
1096
+ ...running.role ? { role: running.role } : {},
1097
+ ...running.project ? { project: running.project } : {}
1098
+ });
1099
+ if (memoryContext.length > 0) {
1100
+ background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
1101
+ }
1102
+ }
1082
1103
  let output;
1083
1104
  try {
1084
1105
  output = await agent.run({
1085
1106
  taskId: running.id,
1086
1107
  objective: running.objective,
1087
1108
  ...role ? { role } : {},
1088
- ...running.context?.background ? { context: running.context.background } : {}
1109
+ ...background !== void 0 ? { context: background } : {}
1089
1110
  });
1090
1111
  } catch (error) {
1091
1112
  const message = error instanceof Error ? error.message : String(error);
@@ -1103,8 +1124,12 @@ var TaskEngine = class {
1103
1124
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1104
1125
  };
1105
1126
  const finalStatus = output.status === "completed" ? "completed" : "failed";
1127
+ const finished = await this.transition(running, finalStatus, { result });
1106
1128
  this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
1107
- return this.transition(running, finalStatus, { result });
1129
+ if (this.options.memory) {
1130
+ await this.options.memory.recordTaskOutcome(finished);
1131
+ }
1132
+ return finished;
1108
1133
  }
1109
1134
  async cancel(id) {
1110
1135
  const task = await this.mustGet(id);
@@ -1149,12 +1174,306 @@ function generateTaskId() {
1149
1174
  return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1150
1175
  }
1151
1176
 
1177
+ // src/memory/local.ts
1178
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
1179
+ import { dirname as dirname2 } from "path";
1180
+ var STOPWORDS = /* @__PURE__ */ new Set([
1181
+ "the",
1182
+ "and",
1183
+ "for",
1184
+ "with",
1185
+ "this",
1186
+ "that",
1187
+ "from",
1188
+ "into",
1189
+ "was",
1190
+ "were",
1191
+ "have",
1192
+ "has",
1193
+ "had",
1194
+ "are",
1195
+ "but",
1196
+ "not",
1197
+ "you",
1198
+ "his",
1199
+ "her",
1200
+ "its",
1201
+ "their",
1202
+ "them",
1203
+ "then",
1204
+ "than",
1205
+ "when",
1206
+ "what",
1207
+ "which",
1208
+ "will",
1209
+ "would",
1210
+ "been",
1211
+ "were",
1212
+ "also",
1213
+ "because",
1214
+ "about",
1215
+ "after",
1216
+ "before"
1217
+ ]);
1218
+ function keywords(text) {
1219
+ return new Set(
1220
+ (text ?? "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4 && !STOPWORDS.has(t))
1221
+ );
1222
+ }
1223
+ function relevanceScore(queryKeywords, entry) {
1224
+ if (queryKeywords.size === 0) return 0;
1225
+ const entryKeywords = keywords(`${entry.content} ${entry.key}`);
1226
+ let overlap = 0;
1227
+ for (const keyword of queryKeywords) {
1228
+ if (entryKeywords.has(keyword)) overlap += 1;
1229
+ }
1230
+ let tagMatches = 0;
1231
+ for (const tag of entry.tags ?? []) {
1232
+ if (queryKeywords.has(tag.toLowerCase())) tagMatches += 1;
1233
+ }
1234
+ return overlap * 2 + tagMatches * 1.5;
1235
+ }
1236
+ var LocalMemoryProvider = class {
1237
+ name = "local";
1238
+ filePath;
1239
+ queue = Promise.resolve();
1240
+ constructor(directory = `${process.cwd()}/.omninode`) {
1241
+ this.filePath = `${directory}/memory.json`;
1242
+ }
1243
+ async query(query) {
1244
+ return this.synchronized(async () => {
1245
+ const entries = await this.load();
1246
+ const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter(
1247
+ (entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
1248
+ );
1249
+ if (!query.text || query.text.trim().length === 0) {
1250
+ return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
1251
+ }
1252
+ const queryKeywords = keywords(query.text);
1253
+ return filtered.map((entry) => ({ entry, score: relevanceScore(queryKeywords, entry) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry }) => entry);
1254
+ });
1255
+ }
1256
+ async write(entry) {
1257
+ return this.synchronized(async () => {
1258
+ const entries = await this.load();
1259
+ const index = entries.findIndex((e) => e.key === entry.key);
1260
+ const stamped = {
1261
+ ...entry,
1262
+ createdAt: entry.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1263
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1264
+ };
1265
+ if (index >= 0) entries[index] = stamped;
1266
+ else entries.push(stamped);
1267
+ await this.saveAll(entries);
1268
+ });
1269
+ }
1270
+ async count() {
1271
+ return (await this.load()).length;
1272
+ }
1273
+ synchronized(operation) {
1274
+ const result = this.queue.then(operation, operation);
1275
+ this.queue = result.then(
1276
+ () => void 0,
1277
+ () => void 0
1278
+ );
1279
+ return result;
1280
+ }
1281
+ async load() {
1282
+ try {
1283
+ const raw = await readFile2(this.filePath, "utf8");
1284
+ const parsed = JSON.parse(raw);
1285
+ return Array.isArray(parsed) ? parsed : [];
1286
+ } catch {
1287
+ return [];
1288
+ }
1289
+ }
1290
+ async saveAll(entries) {
1291
+ const tmp = `${this.filePath}.tmp`;
1292
+ await mkdir2(dirname2(this.filePath), { recursive: true });
1293
+ await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
1294
+ `, "utf8");
1295
+ await rename2(tmp, this.filePath);
1296
+ }
1297
+ };
1298
+
1299
+ // src/memory/remembera.ts
1300
+ var RememberaMemoryProvider = class {
1301
+ name = "remembera";
1302
+ baseUrl;
1303
+ apiKeyEnvVar;
1304
+ env;
1305
+ log;
1306
+ constructor(config, log = logger) {
1307
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
1308
+ this.apiKeyEnvVar = config.apiKeyEnvVar;
1309
+ this.env = config.env ?? process.env;
1310
+ this.log = log.child({ component: "remembera" });
1311
+ }
1312
+ headers() {
1313
+ const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
1314
+ return key ? { authorization: `Bearer ${key}` } : {};
1315
+ }
1316
+ async query(query) {
1317
+ const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
1318
+ if (status !== 200) {
1319
+ throw new MemoryError(`Remembera query returned HTTP ${status}.`);
1320
+ }
1321
+ const entries = body.entries;
1322
+ if (!Array.isArray(entries)) {
1323
+ throw new MemoryError("Remembera query response had no entries array.");
1324
+ }
1325
+ return entries.map((entry) => this.toEntry(entry)).filter((entry) => entry !== void 0);
1326
+ }
1327
+ async write(entry) {
1328
+ const { status } = await this.request(`${this.baseUrl}/api/memory`, entry);
1329
+ if (status !== 200 && status !== 201) {
1330
+ throw new MemoryError(`Remembera write returned HTTP ${status}.`);
1331
+ }
1332
+ }
1333
+ async request(url, body) {
1334
+ const headers = this.headers();
1335
+ try {
1336
+ return await requestJson(url, {
1337
+ method: "POST",
1338
+ headers,
1339
+ body
1340
+ });
1341
+ } catch (error) {
1342
+ const message = error instanceof Error ? error.message : String(error);
1343
+ throw new MemoryError(`Remembera request to ${url} failed: ${message}`, { cause: error });
1344
+ }
1345
+ }
1346
+ toEntry(raw) {
1347
+ if (raw === null || typeof raw !== "object") {
1348
+ this.log.warn("Skipping malformed Remembera memory entry.");
1349
+ return void 0;
1350
+ }
1351
+ const entry = raw;
1352
+ if (typeof entry.key !== "string" || typeof entry.content !== "string") {
1353
+ this.log.warn("Skipping Remembera memory entry without key/content.");
1354
+ return void 0;
1355
+ }
1356
+ const scope = entry.scope;
1357
+ return {
1358
+ key: entry.key,
1359
+ content: entry.content,
1360
+ scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
1361
+ ...Array.isArray(entry.tags) ? { tags: entry.tags.filter((t) => typeof t === "string") } : {},
1362
+ ...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
1363
+ ...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {}
1364
+ };
1365
+ }
1366
+ };
1367
+
1368
+ // src/memory/factory.ts
1369
+ function createMemoryProvider(config) {
1370
+ if (!config || config.provider === "local") {
1371
+ return new LocalMemoryProvider();
1372
+ }
1373
+ if (config.provider === "remembera") {
1374
+ if (!config.baseUrl) {
1375
+ throw new ConfigError(
1376
+ "CONFIG_INVALID",
1377
+ `Memory provider "remembera" needs a base_url in omninode.yaml.`
1378
+ );
1379
+ }
1380
+ return new RememberaMemoryProvider({
1381
+ baseUrl: config.baseUrl,
1382
+ apiKeyEnvVar: config.apiKeyEnvVar
1383
+ });
1384
+ }
1385
+ throw new ConfigError(
1386
+ "CONFIG_INVALID",
1387
+ `Unknown memory provider "${config.provider}". Available: local, remembera.`
1388
+ );
1389
+ }
1390
+
1391
+ // src/memory/service.ts
1392
+ var MAX_CONTEXT_CHARS = 4e3;
1393
+ var MemoryService = class {
1394
+ constructor(provider, log = logger) {
1395
+ this.provider = provider;
1396
+ this.log = log.child({ component: "memory", provider: provider.name });
1397
+ }
1398
+ provider;
1399
+ log;
1400
+ get providerRef() {
1401
+ return this.provider;
1402
+ }
1403
+ get providerName() {
1404
+ return this.provider.name;
1405
+ }
1406
+ /** Relevant context for a task objective; empty string when nothing matches. */
1407
+ async gatherContext(query) {
1408
+ try {
1409
+ const entries = await this.provider.query({
1410
+ text: query.objective,
1411
+ ...query.role ? { tags: [query.role] } : {},
1412
+ limit: 8
1413
+ });
1414
+ if (entries.length === 0) return "";
1415
+ const byScope = /* @__PURE__ */ new Map();
1416
+ for (const entry of entries) {
1417
+ byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
1418
+ }
1419
+ const parts = [];
1420
+ for (const scope of ["project", "task", "role", "knowledge"]) {
1421
+ const scopeEntries = byScope.get(scope) ?? [];
1422
+ if (scopeEntries.length === 0) continue;
1423
+ const lines = scopeEntries.map((entry) => `- ${entry.content}`).join("\n");
1424
+ parts.push(`### Relevant memory (${scope})
1425
+ ${lines}`);
1426
+ }
1427
+ const context = parts.join("\n\n");
1428
+ return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
1429
+ } catch (error) {
1430
+ this.log.warn(
1431
+ `Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
1432
+ );
1433
+ return "";
1434
+ }
1435
+ }
1436
+ /** Records a finished task's outcome — best-effort. */
1437
+ async recordTaskOutcome(task) {
1438
+ const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
1439
+ try {
1440
+ await this.provider.write({
1441
+ key: `task:${task.id}`,
1442
+ scope: "task",
1443
+ content: `Objective: ${task.objective}
1444
+ Status: ${task.status}
1445
+ Outcome: ${outcome}`,
1446
+ tags: ["task", ...task.agent ? [task.agent] : [], ...task.role ? [task.role] : []],
1447
+ createdAt: task.createdAt
1448
+ });
1449
+ const significant = (task.result?.reports ?? []).flatMap(
1450
+ (report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
1451
+ );
1452
+ for (const { report, finding } of significant) {
1453
+ await this.provider.write({
1454
+ key: `finding:${report.id}:${finding.id}`,
1455
+ scope: "project",
1456
+ content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
1457
+ tags: ["known-problem", report.agent]
1458
+ });
1459
+ }
1460
+ } catch (error) {
1461
+ this.log.warn(
1462
+ `Memory write failed (continuing): ${error instanceof Error ? error.message : String(error)}`
1463
+ );
1464
+ }
1465
+ }
1466
+ };
1467
+
1152
1468
  // src/tasks/engine-factory.ts
1153
1469
  function createTaskEngine(config, log = logger, directory) {
1470
+ const agents = buildAgentRegistry(config, log);
1471
+ const roles = buildRoleRegistry(config);
1154
1472
  return new TaskEngine({
1155
- agents: buildAgentRegistry(config, log),
1156
- roles: buildRoleRegistry(config),
1473
+ agents,
1474
+ roles,
1157
1475
  store: new FileTaskStore(directory),
1476
+ ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
1158
1477
  log
1159
1478
  });
1160
1479
  }
@@ -1531,8 +1850,8 @@ function createDefaultChatFn(providers) {
1531
1850
  }
1532
1851
 
1533
1852
  // src/pipelines/store.ts
1534
- import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
1535
- import { dirname as dirname2 } from "path";
1853
+ import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
1854
+ import { dirname as dirname3 } from "path";
1536
1855
  var FilePipelineRunStore = class {
1537
1856
  filePath;
1538
1857
  queue = Promise.resolve();
@@ -1570,7 +1889,7 @@ var FilePipelineRunStore = class {
1570
1889
  }
1571
1890
  async load() {
1572
1891
  try {
1573
- const raw = await readFile2(this.filePath, "utf8");
1892
+ const raw = await readFile3(this.filePath, "utf8");
1574
1893
  const parsed = JSON.parse(raw);
1575
1894
  return Array.isArray(parsed) ? parsed : [];
1576
1895
  } catch {
@@ -1579,10 +1898,10 @@ var FilePipelineRunStore = class {
1579
1898
  }
1580
1899
  async write(runs) {
1581
1900
  const tmp = `${this.filePath}.tmp`;
1582
- await mkdir2(dirname2(this.filePath), { recursive: true });
1583
- await writeFile2(tmp, `${JSON.stringify(runs, null, 2)}
1901
+ await mkdir3(dirname3(this.filePath), { recursive: true });
1902
+ await writeFile3(tmp, `${JSON.stringify(runs, null, 2)}
1584
1903
  `, "utf8");
1585
- await rename2(tmp, this.filePath);
1904
+ await rename3(tmp, this.filePath);
1586
1905
  }
1587
1906
  };
1588
1907
 
@@ -1797,8 +2116,8 @@ var ReportService = class {
1797
2116
  };
1798
2117
 
1799
2118
  // src/reports/store.ts
1800
- import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
1801
- import { dirname as dirname3 } from "path";
2119
+ import { mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
2120
+ import { dirname as dirname4 } from "path";
1802
2121
  var FileReportStore = class {
1803
2122
  filePath;
1804
2123
  queue = Promise.resolve();
@@ -1862,7 +2181,7 @@ var FileReportStore = class {
1862
2181
  }
1863
2182
  async load() {
1864
2183
  try {
1865
- const raw = await readFile3(this.filePath, "utf8");
2184
+ const raw = await readFile4(this.filePath, "utf8");
1866
2185
  const parsed = JSON.parse(raw);
1867
2186
  return Array.isArray(parsed) ? parsed : [];
1868
2187
  } catch {
@@ -1871,10 +2190,10 @@ var FileReportStore = class {
1871
2190
  }
1872
2191
  async write(entries) {
1873
2192
  const tmp = `${this.filePath}.tmp`;
1874
- await mkdir3(dirname3(this.filePath), { recursive: true });
1875
- await writeFile3(tmp, `${JSON.stringify(entries, null, 2)}
2193
+ await mkdir4(dirname4(this.filePath), { recursive: true });
2194
+ await writeFile4(tmp, `${JSON.stringify(entries, null, 2)}
1876
2195
  `, "utf8");
1877
- await rename3(tmp, this.filePath);
2196
+ await rename4(tmp, this.filePath);
1878
2197
  }
1879
2198
  };
1880
2199
 
@@ -1886,6 +2205,7 @@ function buildPipelineEngine(config, log = logger) {
1886
2205
  agents,
1887
2206
  roles,
1888
2207
  store: new FileTaskStore(),
2208
+ ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
1889
2209
  log
1890
2210
  });
1891
2211
  return new PipelineEngine({
@@ -1901,16 +2221,19 @@ function buildPipelineEngine(config, log = logger) {
1901
2221
  }
1902
2222
 
1903
2223
  // src/version.ts
1904
- var OMNINODE_VERSION = "0.7.0";
2224
+ var OMNINODE_VERSION = "0.8.0";
1905
2225
  export {
1906
2226
  AgentError,
1907
2227
  AgentRegistry,
1908
2228
  ConfigError,
1909
2229
  ConsoleLogSink,
1910
2230
  FilePipelineRunStore,
2231
+ FileReportStore,
1911
2232
  FileTaskStore,
2233
+ LocalMemoryProvider,
1912
2234
  Logger,
1913
2235
  MemoryError,
2236
+ MemoryService,
1914
2237
  ModelRegistry,
1915
2238
  OMNINODE_VERSION,
1916
2239
  OPENAI_COMPATIBLE_TYPES,
@@ -1922,10 +2245,13 @@ export {
1922
2245
  ProcessAgent,
1923
2246
  ProtocolError,
1924
2247
  ProviderError,
2248
+ RememberaMemoryProvider,
2249
+ ReportService,
1925
2250
  RoleRegistry,
1926
2251
  TaskEngine,
1927
2252
  TaskError,
1928
2253
  agentConfigSchema,
2254
+ aggregateReports,
1929
2255
  appConfigSchema,
1930
2256
  authHeaders,
1931
2257
  buildAgentRegistry,
@@ -1934,12 +2260,15 @@ export {
1934
2260
  composeTaskText,
1935
2261
  createAgent,
1936
2262
  createDefaultChatFn,
2263
+ createMemoryProvider,
1937
2264
  createProvider,
1938
2265
  createTaskEngine,
1939
2266
  defaultProjectConfigYaml,
1940
2267
  expandEnvRefs,
2268
+ extractReportFromText,
1941
2269
  findConfigFile,
1942
2270
  isOmniNodeError,
2271
+ keywords,
1943
2272
  loadConfig,
1944
2273
  logger,
1945
2274
  pipelineConfigSchema,
@@ -1949,6 +2278,7 @@ export {
1949
2278
  registryKey,
1950
2279
  requestJson,
1951
2280
  resolveApiKey,
1952
- roleConfigSchema
2281
+ roleConfigSchema,
2282
+ similar
1953
2283
  };
1954
2284
  //# sourceMappingURL=index.js.map