@hilbras/omninode 0.4.0 → 0.5.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.d.ts CHANGED
@@ -184,6 +184,7 @@ interface ModelInfo {
184
184
  }
185
185
 
186
186
  /** Task system: every operation becomes a Task (§13). */
187
+
187
188
  type TaskStatus = "created" | "queued" | "running" | "waiting" | "completed" | "failed" | "cancelled";
188
189
  interface TaskContext {
189
190
  project?: string;
@@ -198,6 +199,8 @@ interface TaskResult {
198
199
  summary?: string;
199
200
  artifacts?: string[];
200
201
  reportId?: string;
202
+ /** Structured reports produced by protocol-mode agents (Phase 6 builds on this). */
203
+ reports?: Report[];
201
204
  error?: string;
202
205
  finishedAt?: string;
203
206
  }
@@ -397,7 +400,7 @@ interface IChatProvider extends IProvider {
397
400
  }
398
401
 
399
402
  /** Typed error hierarchy shared across the whole codebase (§4.1 — error model). */
400
- type OmniNodeErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "PROVIDER_NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "PROVIDER_AUTH_FAILED" | "MODEL_NOT_FOUND" | "AGENT_NOT_FOUND" | "AGENT_FAILED" | "AGENT_TIMEOUT" | "TASK_INVALID" | "TASK_FAILED" | "PIPELINE_INVALID" | "PIPELINE_FAILED" | "PROTOCOL_VIOLATION" | "MEMORY_UNAVAILABLE" | "NOT_IMPLEMENTED" | "CLI_USAGE" | "INTERNAL";
403
+ type OmniNodeErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "PROVIDER_NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "PROVIDER_AUTH_FAILED" | "MODEL_NOT_FOUND" | "AGENT_NOT_FOUND" | "AGENT_FAILED" | "AGENT_TIMEOUT" | "TASK_INVALID" | "TASK_NOT_FOUND" | "TASK_FAILED" | "PIPELINE_INVALID" | "PIPELINE_FAILED" | "PROTOCOL_VIOLATION" | "MEMORY_UNAVAILABLE" | "NOT_IMPLEMENTED" | "CLI_USAGE" | "INTERNAL";
401
404
  interface OmniNodeErrorOptions {
402
405
  cause?: unknown;
403
406
  details?: Record<string, unknown>;
@@ -417,7 +420,7 @@ declare class AgentError extends OmniNodeError {
417
420
  constructor(code: "AGENT_NOT_FOUND" | "AGENT_FAILED" | "AGENT_TIMEOUT", message: string, options?: OmniNodeErrorOptions);
418
421
  }
419
422
  declare class TaskError extends OmniNodeError {
420
- constructor(code: "TASK_INVALID" | "TASK_FAILED", message: string, options?: OmniNodeErrorOptions);
423
+ constructor(code: "TASK_INVALID" | "TASK_NOT_FOUND" | "TASK_FAILED", message: string, options?: OmniNodeErrorOptions);
421
424
  }
422
425
  declare class PipelineError extends OmniNodeError {
423
426
  constructor(code: "PIPELINE_INVALID" | "PIPELINE_FAILED", message: string, options?: OmniNodeErrorOptions);
@@ -1099,8 +1102,6 @@ declare class ProcessAgent implements IAgent {
1099
1102
  runningTaskIds(): string[];
1100
1103
  }
1101
1104
 
1102
- declare function createAgent(config: AgentConfig): IAgent;
1103
-
1104
1105
  /** Agent registry (§10): the set of agents connected to OmniNode. */
1105
1106
 
1106
1107
  declare class AgentRegistry {
@@ -1110,6 +1111,79 @@ declare class AgentRegistry {
1110
1111
  list(): IAgent[];
1111
1112
  }
1112
1113
 
1114
+ /**
1115
+ * Agent factory (§9–§10): builds adapter instances from configuration.
1116
+ * "cli" agent configs are mapped to the process integration by the loader.
1117
+ */
1118
+
1119
+ declare function createAgent(config: AgentConfig): IAgent;
1120
+ /** Builds an agent registry from the project configuration, skipping agents that fail to construct. */
1121
+ declare function buildAgentRegistry(config: AppConfig, log?: Logger): AgentRegistry;
1122
+
1123
+ /** Role registry (§11): the set of roles available for task assignment. */
1124
+
1125
+ declare class RoleRegistry {
1126
+ private readonly roles;
1127
+ register(role: RoleDefinition): void;
1128
+ registerAll(roles: RoleDefinition[]): void;
1129
+ get(id: string): RoleDefinition | undefined;
1130
+ list(): RoleDefinition[];
1131
+ }
1132
+ /** Builds a role registry from the project configuration. */
1133
+ declare function buildRoleRegistry(config: AppConfig): RoleRegistry;
1134
+
1135
+ interface TaskStoreFilter {
1136
+ status?: TaskStatus;
1137
+ project?: string;
1138
+ }
1139
+ interface TaskStore {
1140
+ save(task: Task): Promise<void>;
1141
+ get(id: string): Promise<Task | undefined>;
1142
+ list(filter?: TaskStoreFilter): Promise<Task[]>;
1143
+ }
1144
+ declare class FileTaskStore implements TaskStore {
1145
+ private readonly filePath;
1146
+ constructor(directory?: string);
1147
+ save(task: Task): Promise<void>;
1148
+ get(id: string): Promise<Task | undefined>;
1149
+ list(filter?: TaskStoreFilter): Promise<Task[]>;
1150
+ private load;
1151
+ private write;
1152
+ }
1153
+
1154
+ interface CreateTaskInput {
1155
+ objective: string;
1156
+ project?: string;
1157
+ role?: string;
1158
+ agent?: string;
1159
+ context?: TaskContext;
1160
+ }
1161
+ interface TaskEngineOptions {
1162
+ agents: AgentRegistry;
1163
+ roles: RoleRegistry;
1164
+ store: TaskStore;
1165
+ log?: Logger;
1166
+ }
1167
+ declare class TaskEngine {
1168
+ private readonly options;
1169
+ private readonly log;
1170
+ constructor(options: TaskEngineOptions);
1171
+ get store(): TaskStore;
1172
+ create(input: CreateTaskInput): Promise<Task>;
1173
+ queue(id: string): Promise<Task>;
1174
+ /** Runs the task through its assigned agent and records the result. */
1175
+ run(id: string): Promise<Task>;
1176
+ cancel(id: string): Promise<Task>;
1177
+ get(id: string): Promise<Task | undefined>;
1178
+ list(filter?: TaskStoreFilter): Promise<Task[]>;
1179
+ private mustGet;
1180
+ private transition;
1181
+ }
1182
+
1183
+ /** Assembles a TaskEngine from the project configuration (local-first defaults). */
1184
+
1185
+ declare function createTaskEngine(config: AppConfig, log?: Logger, directory?: string): TaskEngine;
1186
+
1113
1187
  declare const OMNINODE_VERSION = "0.4.0";
1114
1188
 
1115
- export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AppConfig, type AppConfigYaml, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type Confidence, ConfigError, ConsoleLogSink, type DiscoveryResult, type EnvelopeMeta, type Finding, type FindingSeverity, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IProvider, type LoadConfigOptions, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryEntry, MemoryError, type MemoryQuery, type MemoryScope, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineDefinition, PipelineError, type PipelineRun, type PipelineRunStatus, type PipelineStep, type PipelineStepCondition, type PipelineStepKind, type PipelineStepRun, ProcessAgent, type Project, type ProjectConfigYaml, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type Report, type RoleConfigYaml, type RoleDefinition, type Task, type TaskContext, TaskError, type TaskResult, type TaskStatus, type WritableLike, agentConfigSchema, appConfigSchema, authHeaders, composeTaskText, createAgent, createProvider, defaultProjectConfigYaml, expandEnvRefs, findConfigFile, isOmniNodeError, loadConfig, logger, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema };
1189
+ export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AppConfig, type AppConfigYaml, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type DiscoveryResult, type EnvelopeMeta, FileTaskStore, type Finding, type FindingSeverity, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IProvider, type LoadConfigOptions, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryEntry, MemoryError, type MemoryQuery, type MemoryScope, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineDefinition, PipelineError, type PipelineRun, type PipelineRunStatus, type PipelineStep, type PipelineStepCondition, type PipelineStepKind, type PipelineStepRun, ProcessAgent, type Project, type ProjectConfigYaml, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type Report, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, appConfigSchema, authHeaders, buildAgentRegistry, buildRoleRegistry, composeTaskText, createAgent, createProvider, createTaskEngine, defaultProjectConfigYaml, expandEnvRefs, findConfigFile, isOmniNodeError, loadConfig, logger, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema };
package/dist/index.js CHANGED
@@ -846,6 +846,20 @@ function firstLine(text, maxLength = 200) {
846
846
  return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
847
847
  }
848
848
 
849
+ // src/agents/registry.ts
850
+ var AgentRegistry = class {
851
+ agents = /* @__PURE__ */ new Map();
852
+ register(agent) {
853
+ this.agents.set(agent.info.name, agent);
854
+ }
855
+ get(name) {
856
+ return this.agents.get(name);
857
+ }
858
+ list() {
859
+ return [...this.agents.values()].sort((a, b) => a.info.name.localeCompare(b.info.name));
860
+ }
861
+ };
862
+
849
863
  // src/agents/factory.ts
850
864
  function createAgent(config) {
851
865
  if (config.integration === "process") {
@@ -859,20 +873,233 @@ function createAgent(config) {
859
873
  }
860
874
  throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
861
875
  }
876
+ function buildAgentRegistry(config, log = logger) {
877
+ const registry = new AgentRegistry();
878
+ for (const agentConfig of config.project.agents) {
879
+ try {
880
+ registry.register(createAgent(agentConfig));
881
+ } catch (error) {
882
+ log.warn(
883
+ `Skipping agent "${agentConfig.name}": ${error instanceof Error ? error.message : String(error)}`
884
+ );
885
+ }
886
+ }
887
+ return registry;
888
+ }
862
889
 
863
- // src/agents/registry.ts
864
- var AgentRegistry = class {
865
- agents = /* @__PURE__ */ new Map();
866
- register(agent) {
867
- this.agents.set(agent.info.name, agent);
890
+ // src/roles/index.ts
891
+ var RoleRegistry = class {
892
+ roles = /* @__PURE__ */ new Map();
893
+ register(role) {
894
+ this.roles.set(role.id, role);
868
895
  }
869
- get(name) {
870
- return this.agents.get(name);
896
+ registerAll(roles) {
897
+ for (const role of roles) this.register(role);
898
+ }
899
+ get(id) {
900
+ return this.roles.get(id);
871
901
  }
872
902
  list() {
873
- return [...this.agents.values()].sort((a, b) => a.info.name.localeCompare(b.info.name));
903
+ return [...this.roles.values()].sort((a, b) => a.id.localeCompare(b.id));
904
+ }
905
+ };
906
+ function buildRoleRegistry(config) {
907
+ const registry = new RoleRegistry();
908
+ registry.registerAll(config.project.roles);
909
+ return registry;
910
+ }
911
+
912
+ // src/tasks/store.ts
913
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
914
+ import { dirname } from "path";
915
+ var FileTaskStore = class {
916
+ filePath;
917
+ constructor(directory = `${process.cwd()}/.omninode`) {
918
+ this.filePath = `${directory}/tasks.json`;
919
+ }
920
+ async save(task) {
921
+ const tasks = await this.load();
922
+ const index = tasks.findIndex((t) => t.id === task.id);
923
+ if (index >= 0) tasks[index] = task;
924
+ else tasks.push(task);
925
+ await this.write(tasks);
926
+ }
927
+ async get(id) {
928
+ const tasks = await this.load();
929
+ return tasks.find((t) => t.id === id);
930
+ }
931
+ async list(filter = {}) {
932
+ const tasks = await this.load();
933
+ return tasks.filter((task) => filter.status ? task.status === filter.status : true).filter((task) => filter.project ? task.project === filter.project : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
934
+ }
935
+ async load() {
936
+ try {
937
+ const raw = await readFile(this.filePath, "utf8");
938
+ const parsed = JSON.parse(raw);
939
+ return Array.isArray(parsed) ? parsed : [];
940
+ } catch {
941
+ return [];
942
+ }
874
943
  }
944
+ async write(tasks) {
945
+ const tmp = `${this.filePath}.tmp`;
946
+ await mkdir(dirname(this.filePath), { recursive: true });
947
+ await writeFile(tmp, `${JSON.stringify(tasks, null, 2)}
948
+ `, "utf8");
949
+ await rename(tmp, this.filePath);
950
+ }
951
+ };
952
+
953
+ // src/tasks/engine.ts
954
+ import { randomBytes } from "crypto";
955
+ var ALLOWED_TRANSITIONS = {
956
+ created: ["queued", "running", "cancelled"],
957
+ queued: ["running", "cancelled"],
958
+ running: ["completed", "failed", "cancelled"],
959
+ waiting: ["running", "cancelled"],
960
+ completed: [],
961
+ failed: [],
962
+ cancelled: []
875
963
  };
964
+ var TaskEngine = class {
965
+ constructor(options) {
966
+ this.options = options;
967
+ this.log = (options.log ?? logger).child({ component: "tasks" });
968
+ }
969
+ options;
970
+ log;
971
+ get store() {
972
+ return this.options.store;
973
+ }
974
+ async create(input) {
975
+ if (!input.objective || input.objective.trim().length === 0) {
976
+ throw new TaskError("TASK_INVALID", "Task objective must not be empty.");
977
+ }
978
+ if (input.role && !this.options.roles.get(input.role)) {
979
+ throw new TaskError(
980
+ "TASK_INVALID",
981
+ `Role "${input.role}" is not defined. Add it under project.roles in omninode.yaml.`
982
+ );
983
+ }
984
+ if (input.agent && !this.options.agents.get(input.agent)) {
985
+ throw new TaskError(
986
+ "TASK_INVALID",
987
+ `Agent "${input.agent}" is not registered. Add it with \`omninode agent add\`.`
988
+ );
989
+ }
990
+ const now = (/* @__PURE__ */ new Date()).toISOString();
991
+ const task = {
992
+ id: generateTaskId(),
993
+ objective: input.objective.trim(),
994
+ status: "created",
995
+ createdAt: now,
996
+ updatedAt: now,
997
+ ...input.project !== void 0 ? { project: input.project } : {},
998
+ ...input.role !== void 0 ? { role: input.role } : {},
999
+ ...input.agent !== void 0 ? { agent: input.agent } : {},
1000
+ ...input.context !== void 0 ? { context: input.context } : {}
1001
+ };
1002
+ await this.options.store.save(task);
1003
+ this.log.info(`Created task ${task.id}.`);
1004
+ return task;
1005
+ }
1006
+ async queue(id) {
1007
+ const task = await this.mustGet(id);
1008
+ return this.transition(task, "queued");
1009
+ }
1010
+ /** Runs the task through its assigned agent and records the result. */
1011
+ async run(id) {
1012
+ const task = await this.mustGet(id);
1013
+ if (task.status !== "created" && task.status !== "queued") {
1014
+ throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
1015
+ }
1016
+ if (!task.agent) {
1017
+ throw new TaskError("TASK_INVALID", `Task ${id} has no agent assigned. Re-create it with --agent.`);
1018
+ }
1019
+ const agent = this.options.agents.get(task.agent);
1020
+ if (!agent) {
1021
+ throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
1022
+ }
1023
+ const role = task.role ? this.options.roles.get(task.role) : void 0;
1024
+ const running = await this.transition(task, "running");
1025
+ this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
1026
+ let output;
1027
+ try {
1028
+ output = await agent.run({
1029
+ taskId: running.id,
1030
+ objective: running.objective,
1031
+ ...role ? { role } : {},
1032
+ ...running.context?.background ? { context: running.context.background } : {}
1033
+ });
1034
+ } catch (error) {
1035
+ const message = error instanceof Error ? error.message : String(error);
1036
+ this.log.error(`Task ${task.id} failed: ${message}`);
1037
+ return this.transition(running, "failed", {
1038
+ result: { error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }
1039
+ });
1040
+ }
1041
+ const result = {
1042
+ ...output.summary !== void 0 ? { summary: output.summary } : {},
1043
+ ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
1044
+ ...output.error !== void 0 ? { error: output.error } : {},
1045
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1046
+ };
1047
+ const finalStatus = output.status === "completed" ? "completed" : "failed";
1048
+ this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
1049
+ return this.transition(running, finalStatus, { result });
1050
+ }
1051
+ async cancel(id) {
1052
+ const task = await this.mustGet(id);
1053
+ if (!ALLOWED_TRANSITIONS[task.status].includes("cancelled")) {
1054
+ throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be cancelled.`);
1055
+ }
1056
+ if (task.agent) {
1057
+ const agent = this.options.agents.get(task.agent);
1058
+ await agent?.cancel?.(task.id);
1059
+ }
1060
+ return this.transition(task, "cancelled");
1061
+ }
1062
+ async get(id) {
1063
+ return this.options.store.get(id);
1064
+ }
1065
+ async list(filter) {
1066
+ return this.options.store.list(filter);
1067
+ }
1068
+ async mustGet(id) {
1069
+ const task = await this.options.store.get(id);
1070
+ if (!task) {
1071
+ throw new TaskError("TASK_NOT_FOUND", `Task "${id}" does not exist in this project's task store.`);
1072
+ }
1073
+ return task;
1074
+ }
1075
+ async transition(task, to, extra = {}) {
1076
+ const allowed = ALLOWED_TRANSITIONS[task.status] ?? [];
1077
+ if (!allowed.includes(to)) {
1078
+ throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
1079
+ }
1080
+ const updated = {
1081
+ ...task,
1082
+ status: to,
1083
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1084
+ ...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
1085
+ };
1086
+ await this.options.store.save(updated);
1087
+ return updated;
1088
+ }
1089
+ };
1090
+ function generateTaskId() {
1091
+ return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1092
+ }
1093
+
1094
+ // src/tasks/engine-factory.ts
1095
+ function createTaskEngine(config, log = logger, directory) {
1096
+ return new TaskEngine({
1097
+ agents: buildAgentRegistry(config, log),
1098
+ roles: buildRoleRegistry(config),
1099
+ store: new FileTaskStore(directory),
1100
+ log
1101
+ });
1102
+ }
876
1103
 
877
1104
  // src/version.ts
878
1105
  var OMNINODE_VERSION = "0.4.0";
@@ -881,6 +1108,7 @@ export {
881
1108
  AgentRegistry,
882
1109
  ConfigError,
883
1110
  ConsoleLogSink,
1111
+ FileTaskStore,
884
1112
  Logger,
885
1113
  MemoryError,
886
1114
  ModelRegistry,
@@ -893,13 +1121,18 @@ export {
893
1121
  ProcessAgent,
894
1122
  ProtocolError,
895
1123
  ProviderError,
1124
+ RoleRegistry,
1125
+ TaskEngine,
896
1126
  TaskError,
897
1127
  agentConfigSchema,
898
1128
  appConfigSchema,
899
1129
  authHeaders,
1130
+ buildAgentRegistry,
1131
+ buildRoleRegistry,
900
1132
  composeTaskText,
901
1133
  createAgent,
902
1134
  createProvider,
1135
+ createTaskEngine,
903
1136
  defaultProjectConfigYaml,
904
1137
  expandEnvRefs,
905
1138
  findConfigFile,