@hilbras/omninode 2.0.0-alpha.13 → 2.0.0-alpha.14

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
@@ -80,16 +80,19 @@ var ConsoleLogSink = class {
80
80
  };
81
81
  var Logger = class _Logger {
82
82
  level;
83
+ format;
83
84
  sink;
84
85
  context;
85
86
  constructor(options = {}) {
86
87
  this.level = options.level ?? "info";
88
+ this.format = options.format ?? "text";
87
89
  this.sink = options.sink ?? new ConsoleLogSink();
88
90
  this.context = options.context ?? {};
89
91
  }
90
92
  child(context) {
91
93
  return new _Logger({
92
94
  level: this.level,
95
+ format: this.format,
93
96
  sink: this.sink,
94
97
  context: { ...this.context, ...context }
95
98
  });
@@ -108,8 +111,13 @@ var Logger = class _Logger {
108
111
  }
109
112
  log(level, message, data) {
110
113
  if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) return;
111
- const parts = [(/* @__PURE__ */ new Date()).toISOString(), level.toUpperCase(), message];
114
+ const at = (/* @__PURE__ */ new Date()).toISOString();
112
115
  const merged = { ...this.context, ...data };
116
+ if (this.format === "json") {
117
+ this.sink.write(level, JSON.stringify({ at, level, message, ...merged }));
118
+ return;
119
+ }
120
+ const parts = [at, level.toUpperCase(), message];
113
121
  if (Object.keys(merged).length > 0) {
114
122
  parts.push(JSON.stringify(merged));
115
123
  }
@@ -117,6 +125,10 @@ var Logger = class _Logger {
117
125
  }
118
126
  };
119
127
  var logger = new Logger();
128
+ function configureLogger(options) {
129
+ if (options.level !== void 0) logger.level = options.level;
130
+ if (options.format !== void 0) logger.format = options.format;
131
+ }
120
132
 
121
133
  // src/config/schema.ts
122
134
  import { z } from "zod";
@@ -199,7 +211,10 @@ var projectConfigSchema = z.object({
199
211
  }).strict();
200
212
  var appConfigSchema = z.object({
201
213
  project: projectConfigSchema,
202
- logging: z.object({ level: z.enum(["debug", "info", "warn", "error"]) }).strict().optional()
214
+ logging: z.object({
215
+ level: z.enum(["debug", "info", "warn", "error"]),
216
+ format: z.enum(["text", "json"]).optional()
217
+ }).strict().optional()
203
218
  }).strict();
204
219
 
205
220
  // src/config/secrets.ts
@@ -578,7 +593,9 @@ var OpenAICompatibleProvider = class {
578
593
  /** Discovered models, so getModel() answers without another round-trip. */
579
594
  discovered;
580
595
  env;
581
- constructor(config, log = logger, env = process.env) {
596
+ audit;
597
+ constructor(config, log = logger, env = process.env, audit) {
598
+ this.audit = audit;
582
599
  this.config = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
583
600
  this.env = env;
584
601
  this.providerId = config.providerId ?? config.name;
@@ -617,17 +634,28 @@ var OpenAICompatibleProvider = class {
617
634
  timeoutMs: this.requestTimeoutMs
618
635
  });
619
636
  } catch (error) {
620
- if (error instanceof ProviderError) throw error;
621
- throw providerTransportError({ provider: this.config.name, operation: "listModels", cause: error });
637
+ if (error instanceof ProviderError) {
638
+ await this.recordProviderError("listModels", error);
639
+ throw error;
640
+ }
641
+ const normalized = providerTransportError({
642
+ provider: this.config.name,
643
+ operation: "listModels",
644
+ cause: error
645
+ });
646
+ await this.recordProviderError("listModels", normalized);
647
+ throw normalized;
622
648
  }
623
649
  const { status, body } = response;
624
650
  if (status !== 200) {
625
- throw providerHttpError({
651
+ const httpError = providerHttpError({
626
652
  provider: this.config.name,
627
653
  operation: "listModels",
628
654
  status,
629
655
  body
630
656
  });
657
+ await this.recordProviderError("listModels", httpError);
658
+ throw httpError;
631
659
  }
632
660
  return response;
633
661
  }
@@ -679,6 +707,21 @@ var OpenAICompatibleProvider = class {
679
707
  stream(request, onChunk) {
680
708
  return streamChatCompletion(this.config, request, onChunk, this.env);
681
709
  }
710
+ /** Provider errors are part of the audit trail (§14 correlation, §18 observability). */
711
+ async recordProviderError(operation, error) {
712
+ await this.audit?.record({
713
+ at: (/* @__PURE__ */ new Date()).toISOString(),
714
+ action: "provider.error",
715
+ id: this.providerId,
716
+ providerId: this.providerId,
717
+ detail: {
718
+ operation,
719
+ code: error.code,
720
+ kind: error.details?.kind,
721
+ message: error.message
722
+ }
723
+ });
724
+ }
682
725
  async healthCheck() {
683
726
  const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
684
727
  try {
@@ -723,12 +766,17 @@ var OpenAICompatibleProvider = class {
723
766
  }
724
767
  });
725
768
  } catch (error) {
726
- if (error instanceof ProviderError) throw error;
727
- throw providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
769
+ if (error instanceof ProviderError) {
770
+ await this.recordProviderError("chat", error);
771
+ throw error;
772
+ }
773
+ const normalized = providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
774
+ await this.recordProviderError("chat", normalized);
775
+ throw normalized;
728
776
  }
729
777
  const { status, body } = response;
730
778
  if (status !== 200) {
731
- throw providerHttpError({
779
+ const httpError = providerHttpError({
732
780
  provider: this.config.name,
733
781
  operation: "chat",
734
782
  status,
@@ -737,6 +785,8 @@ var OpenAICompatibleProvider = class {
737
785
  code: "MODEL_NOT_FOUND"
738
786
  } : {}
739
787
  });
788
+ await this.recordProviderError("chat", httpError);
789
+ throw httpError;
740
790
  }
741
791
  const payload = body;
742
792
  const choices = Array.isArray(payload.choices) ? payload.choices : [];
@@ -929,13 +979,18 @@ var OPENAI_COMPATIBLE_TYPES = [
929
979
  "local",
930
980
  "custom"
931
981
  ];
932
- function createProvider(config) {
982
+ function createProvider(config, options = {}) {
983
+ const shared = {
984
+ ...options.env !== void 0 ? { env: options.env } : {},
985
+ ...options.audit !== void 0 ? { audit: options.audit } : {}
986
+ };
933
987
  if (config.type === "omnihilbras") {
934
- return new OmniHilbrasProvider(config);
988
+ return new OmniHilbrasProvider(config, void 0, options.env, options.audit);
935
989
  }
936
990
  if (OPENAI_COMPATIBLE_TYPES.includes(config.type)) {
937
- return new OpenAICompatibleProvider(config);
991
+ return new OpenAICompatibleProvider(config, void 0, options.env, options.audit);
938
992
  }
993
+ void shared;
939
994
  throw new OmniNodeError("NOT_IMPLEMENTED", `Provider type "${config.type}" has no adapter yet.`);
940
995
  }
941
996
 
@@ -1857,28 +1912,34 @@ function buildRoleRegistry(config) {
1857
1912
  }
1858
1913
 
1859
1914
  // src/persistence/json-file-store.ts
1860
- import { open, mkdir, readFile, rename, stat } from "fs/promises";
1915
+ import { open, mkdir, readFile, rename, stat, writeFile } from "fs/promises";
1861
1916
  import { dirname } from "path";
1862
1917
  var SCHEMA_VERSION = 2;
1863
- async function writeFileAtomic(target, contents) {
1918
+ async function writeFileAtomic(target, contents, durability = "rename") {
1864
1919
  const tmp = `${target}.tmp`;
1865
1920
  await mkdir(dirname(target), { recursive: true });
1866
- const handle = await open(tmp, "w");
1867
- try {
1868
- await handle.writeFile(contents, "utf8");
1869
- await handle.sync();
1870
- } finally {
1871
- await handle.close();
1921
+ if (durability === "fsync") {
1922
+ const handle = await open(tmp, "w");
1923
+ try {
1924
+ await handle.writeFile(contents, "utf8");
1925
+ await handle.sync();
1926
+ } finally {
1927
+ await handle.close();
1928
+ }
1929
+ } else {
1930
+ await writeFile(tmp, contents, "utf8");
1872
1931
  }
1873
1932
  await rename(tmp, target);
1874
- try {
1875
- const dir = await open(dirname(target), "r");
1933
+ if (durability === "fsync") {
1876
1934
  try {
1877
- await dir.sync();
1878
- } finally {
1879
- await dir.close();
1935
+ const dir = await open(dirname(target), "r");
1936
+ try {
1937
+ await dir.sync();
1938
+ } finally {
1939
+ await dir.close();
1940
+ }
1941
+ } catch {
1880
1942
  }
1881
- } catch {
1882
1943
  }
1883
1944
  }
1884
1945
  async function inspectDocument(path) {
@@ -1948,6 +2009,7 @@ async function readDocument(path, onCorrupt, quarantine = true) {
1948
2009
  var JsonFileStore = class {
1949
2010
  filePath;
1950
2011
  schemaVersion;
2012
+ durability;
1951
2013
  onCorrupt;
1952
2014
  quarantine;
1953
2015
  log;
@@ -1955,6 +2017,7 @@ var JsonFileStore = class {
1955
2017
  constructor(options) {
1956
2018
  this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
1957
2019
  this.schemaVersion = options.schemaVersion ?? SCHEMA_VERSION;
2020
+ this.durability = options.durability ?? "rename";
1958
2021
  this.onCorrupt = options.onCorrupt;
1959
2022
  this.quarantine = options.quarantine ?? true;
1960
2023
  this.log = logger.child({ component: "persistence", file: options.fileName });
@@ -1969,7 +2032,7 @@ var JsonFileStore = class {
1969
2032
  async writeAll(items) {
1970
2033
  const document = { schemaVersion: this.schemaVersion, items };
1971
2034
  await writeFileAtomic(this.filePath, `${JSON.stringify(document, null, 2)}
1972
- `);
2035
+ `, this.durability);
1973
2036
  }
1974
2037
  /** Serializes an async operation against this file. */
1975
2038
  synchronized(operation) {
@@ -2066,13 +2129,14 @@ var TaskEngine = class {
2066
2129
  createdAt: now,
2067
2130
  updatedAt: now,
2068
2131
  ...input.project !== void 0 ? { project: input.project } : {},
2132
+ ...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
2069
2133
  ...input.role !== void 0 ? { role: input.role } : {},
2070
2134
  ...input.agent !== void 0 ? { agent: input.agent } : {},
2071
2135
  ...input.context !== void 0 ? { context: input.context } : {}
2072
2136
  };
2073
2137
  await this.options.store.save(task);
2074
2138
  this.log.info(`Created task ${task.id}.`);
2075
- await this.audit("task.created", task.id, { objective: task.objective, project: task.project });
2139
+ await this.audit("task.created", task, { objective: task.objective });
2076
2140
  return task;
2077
2141
  }
2078
2142
  async queue(id) {
@@ -2115,12 +2179,24 @@ var TaskEngine = class {
2115
2179
  const executionId = `exec-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
2116
2180
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2117
2181
  current = await this.transition(current, "running", { attempt });
2182
+ await this.options.audit?.record({
2183
+ at: startedAt,
2184
+ action: "agent.started",
2185
+ id: current.id,
2186
+ taskId: current.id,
2187
+ ...current.pipelineId !== void 0 ? { pipelineId: current.pipelineId } : {},
2188
+ agentId: task.agent,
2189
+ executionId,
2190
+ detail: { attempt }
2191
+ });
2118
2192
  let output;
2119
2193
  let thrown;
2120
2194
  try {
2121
2195
  output = await agent.run({
2122
2196
  taskId: current.id,
2123
2197
  objective: current.objective,
2198
+ executionId,
2199
+ ...current.pipelineId !== void 0 ? { pipelineId: current.pipelineId } : {},
2124
2200
  ...role ? { role } : {},
2125
2201
  ...background !== void 0 ? { context: background } : {}
2126
2202
  });
@@ -2128,6 +2204,16 @@ var TaskEngine = class {
2128
2204
  thrown = error;
2129
2205
  }
2130
2206
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2207
+ await this.options.audit?.record({
2208
+ at: finishedAt,
2209
+ action: "agent.completed",
2210
+ id: current.id,
2211
+ taskId: current.id,
2212
+ ...current.pipelineId !== void 0 ? { pipelineId: current.pipelineId } : {},
2213
+ agentId: task.agent,
2214
+ executionId,
2215
+ detail: { attempt, outcome: thrown !== void 0 || output === void 0 ? "threw" : "returned" }
2216
+ });
2131
2217
  if (thrown !== void 0 || output === void 0) {
2132
2218
  const outcome = classifyThrown(thrown);
2133
2219
  const message = thrown instanceof Error ? thrown.message : String(thrown);
@@ -2207,7 +2293,7 @@ var TaskEngine = class {
2207
2293
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2208
2294
  };
2209
2295
  await this.options.store.save(reset);
2210
- await this.audit("task.retry", id, { from: task.status });
2296
+ await this.audit("task.retry", reset, { from: task.status });
2211
2297
  this.log.info(`Task ${id} reset for retry.`);
2212
2298
  return reset;
2213
2299
  }
@@ -2240,16 +2326,21 @@ var TaskEngine = class {
2240
2326
  ...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
2241
2327
  };
2242
2328
  await this.options.store.save(updated);
2243
- await this.audit(`task.${to === "queued" ? "queued" : to}`, updated.id, {
2329
+ await this.audit(`task.${to === "queued" ? "queued" : to}`, updated, {
2244
2330
  from: task.status
2245
2331
  });
2246
2332
  return updated;
2247
2333
  }
2248
- async audit(action, id, detail) {
2334
+ async audit(action, task, detail, executionId) {
2249
2335
  await this.options.audit?.record({
2250
2336
  at: (/* @__PURE__ */ new Date()).toISOString(),
2251
2337
  action,
2252
- id,
2338
+ id: task.id,
2339
+ taskId: task.id,
2340
+ ...task.project !== void 0 ? { project: task.project } : {},
2341
+ ...task.pipelineId !== void 0 ? { pipelineId: task.pipelineId } : {},
2342
+ ...task.agent !== void 0 ? { agentId: task.agent } : {},
2343
+ ...executionId !== void 0 ? { executionId } : {},
2253
2344
  ...detail !== void 0 ? { detail } : {}
2254
2345
  });
2255
2346
  }
@@ -2272,7 +2363,7 @@ function sleep(ms) {
2272
2363
  }
2273
2364
 
2274
2365
  // src/audit/index.ts
2275
- import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2366
+ import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2276
2367
  import { dirname as dirname2 } from "path";
2277
2368
  var FileAuditLog = class {
2278
2369
  filePath;
@@ -2284,15 +2375,8 @@ var FileAuditLog = class {
2284
2375
  async record(event) {
2285
2376
  try {
2286
2377
  await mkdir2(dirname2(this.filePath), { recursive: true });
2287
- const { open: open2 } = await import("fs/promises");
2288
- const handle = await open2(this.filePath, "a");
2289
- try {
2290
- await handle.appendFile(`${JSON.stringify(event)}
2378
+ await appendFile(this.filePath, `${JSON.stringify(event)}
2291
2379
  `, "utf8");
2292
- await handle.sync();
2293
- } finally {
2294
- await handle.close();
2295
- }
2296
2380
  } catch (error) {
2297
2381
  this.log.warn(
2298
2382
  `Audit write failed: ${error instanceof Error ? error.message : String(error)}`
@@ -3431,6 +3515,7 @@ var PipelineEngine = class {
3431
3515
  at: (/* @__PURE__ */ new Date()).toISOString(),
3432
3516
  action: "pipeline.run.started",
3433
3517
  id: run.id,
3518
+ pipelineId: def.id,
3434
3519
  detail: { pipeline: def.id, objective }
3435
3520
  });
3436
3521
  const deps = this.effectiveDependencies(def);
@@ -3471,7 +3556,9 @@ var PipelineEngine = class {
3471
3556
  combinedContext: contextParts.join("\n\n"),
3472
3557
  reports: [...allReports],
3473
3558
  pipelineRunId: run.id,
3474
- runId: run.id
3559
+ runId: run.id,
3560
+ pipelineDefId: def.id,
3561
+ taskId: run.id
3475
3562
  };
3476
3563
  return this.executeStep(step, ctx);
3477
3564
  })
@@ -3548,8 +3635,9 @@ ${contribution}`);
3548
3635
  this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
3549
3636
  await this.options.audit?.record({
3550
3637
  at: (/* @__PURE__ */ new Date()).toISOString(),
3551
- action: "pipeline.run.finished",
3638
+ action: cancelled ? "pipeline.run.cancelled" : finalStatus === "failed" ? "pipeline.run.failed" : "pipeline.run.finished",
3552
3639
  id: run.id,
3640
+ pipelineId: def.id,
3553
3641
  detail: { pipeline: def.id, status: finalStatus }
3554
3642
  });
3555
3643
  return finished;
@@ -3630,6 +3718,7 @@ ${contribution}`);
3630
3718
  agentNames.map(
3631
3719
  (name) => this.options.tasks.create({
3632
3720
  objective: ctx.objective,
3721
+ ...ctx.pipelineRunId !== void 0 ? { pipelineId: ctx.pipelineRunId } : {},
3633
3722
  agent: name,
3634
3723
  ...ctx.role ? { role: ctx.role.id } : {},
3635
3724
  ...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
@@ -3672,6 +3761,7 @@ ${contribution}`);
3672
3761
  async runSingle(step, ctx, agentName) {
3673
3762
  const task = await this.options.tasks.create({
3674
3763
  objective: ctx.objective,
3764
+ ...ctx.pipelineRunId !== void 0 ? { pipelineId: ctx.pipelineRunId } : {},
3675
3765
  agent: agentName,
3676
3766
  ...ctx.role ? { role: ctx.role.id } : {},
3677
3767
  ...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
@@ -3742,7 +3832,13 @@ ${contribution}`);
3742
3832
  at: (/* @__PURE__ */ new Date()).toISOString(),
3743
3833
  action: "plan.generated",
3744
3834
  id: plan.id,
3745
- detail: { generatedBy: plan.generatedBy, steps: plan.steps.length }
3835
+ ...ctx.pipelineDefId !== void 0 ? { pipelineId: ctx.pipelineDefId } : {},
3836
+ detail: {
3837
+ generatedBy: plan.generatedBy,
3838
+ steps: plan.steps.length,
3839
+ run: ctx.pipelineRunId,
3840
+ taskId: ctx.taskId
3841
+ }
3746
3842
  });
3747
3843
  return {
3748
3844
  status: "completed",
@@ -3776,7 +3872,7 @@ function formatPlan(plan) {
3776
3872
  }
3777
3873
  return lines.join("\n");
3778
3874
  }
3779
- function createDefaultChatFn(providers) {
3875
+ function createDefaultChatFn(providers, options = {}) {
3780
3876
  return async (modelRef, messages) => {
3781
3877
  const separator = modelRef.indexOf(":");
3782
3878
  if (separator <= 0 || separator === modelRef.length - 1) {
@@ -3794,7 +3890,10 @@ function createDefaultChatFn(providers) {
3794
3890
  `Provider "${providerName}" (model "${modelRef}") is not configured.`
3795
3891
  );
3796
3892
  }
3797
- const provider = createProvider(providerConfig);
3893
+ const provider = createProvider(providerConfig, {
3894
+ ...options.audit !== void 0 ? { audit: options.audit } : {},
3895
+ ...options.env !== void 0 ? { env: options.env } : {}
3896
+ });
3798
3897
  const response = await provider.chat({ model: modelId, messages });
3799
3898
  return response.content;
3800
3899
  };
@@ -4051,9 +4150,10 @@ var FileReportStore = class extends JsonFileStore {
4051
4150
 
4052
4151
  // src/pipelines/engine-factory.ts
4053
4152
  function buildPipelineEngine(config, log = logger) {
4153
+ const auditLog = new FileAuditLog();
4054
4154
  const agents = buildAgentRegistry(config, log);
4055
4155
  const roles = buildRoleRegistry(config);
4056
- const chat = createDefaultChatFn(config.project.providers);
4156
+ const chat = createDefaultChatFn(config.project.providers, { audit: auditLog });
4057
4157
  const tasks = new TaskEngine({
4058
4158
  agents,
4059
4159
  roles,
@@ -4150,7 +4250,7 @@ async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, op
4150
4250
  }
4151
4251
 
4152
4252
  // src/version.ts
4153
- var OMNINODE_VERSION = "2.0.0-alpha.13";
4253
+ var OMNINODE_VERSION = "2.0.0-alpha.14";
4154
4254
  export {
4155
4255
  AgentAdapterRegistry,
4156
4256
  AgentError,
@@ -4212,6 +4312,7 @@ export {
4212
4312
  buildPlannerContext,
4213
4313
  buildRoleRegistry,
4214
4314
  composeTaskText,
4315
+ configureLogger,
4215
4316
  createAgent,
4216
4317
  createDefaultChatFn,
4217
4318
  createMemoryProvider,