@hilbras/omninode 2.0.0-alpha.1 → 2.0.0-alpha.2

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
@@ -229,7 +229,27 @@ interface ModelInfo {
229
229
 
230
230
  /** Task system: every operation becomes a Task (§13). */
231
231
 
232
- type TaskStatus = "created" | "queued" | "running" | "waiting" | "completed" | "failed" | "cancelled";
232
+ /**
233
+ * Execution states (v2 Phase 2, roadmap §6.1).
234
+ *
235
+ * The crucial distinction: `unknown` means "we cannot prove the outcome" —
236
+ * e.g. a process timeout after the agent may already have performed side
237
+ * effects. It is deliberately NOT `failed`: retrying blindly could duplicate
238
+ * work, and reporting failure would be a lie. Resolve it explicitly with
239
+ * `task retry` (which starts a fresh execution) or by inspecting artifacts.
240
+ */
241
+ type TaskStatus = "created" | "queued" | "running" | "waiting" | "completed" | "failed" | "timed_out" | "cancelled" | "unknown" | "partially_completed";
242
+ /** States from which no further automatic transition happens. */
243
+ declare const TERMINAL_TASK_STATUSES: readonly TaskStatus[];
244
+ /** One execution attempt (roadmap §6.5 identity: task + attempt + execution id). */
245
+ interface ExecutionRecord {
246
+ executionId: string;
247
+ attempt: number;
248
+ startedAt: string;
249
+ finishedAt: string;
250
+ outcome: "completed" | "failed" | "timed_out" | "unknown" | "cancelled";
251
+ error?: string;
252
+ }
233
253
  interface TaskContext {
234
254
  project?: string;
235
255
  /** Paths or globs relevant to the task. */
@@ -262,6 +282,12 @@ interface Task {
262
282
  status: TaskStatus;
263
283
  createdAt: string;
264
284
  updatedAt: string;
285
+ /** Number of engine.run attempts so far (0 = never executed). */
286
+ attempt: number;
287
+ /** Error from the most recent failed/unknown attempt. */
288
+ lastError?: string;
289
+ /** Recent execution attempts, newest last, bounded in length. */
290
+ executions?: ExecutionRecord[];
265
291
  result?: TaskResult;
266
292
  }
267
293
 
@@ -292,7 +318,12 @@ interface PipelineDefinition {
292
318
  role?: string;
293
319
  steps: PipelineStep[];
294
320
  }
295
- type PipelineRunStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
321
+ /**
322
+ * Run status. "partial" means the run finished but some step outcome was
323
+ * incomplete or unprovable (partial fan-out, unknown task state) — the
324
+ * authoritative detail always lives in `stepRuns`/`taskRuns`.
325
+ */
326
+ type PipelineRunStatus = "pending" | "running" | "completed" | "failed" | "partial" | "cancelled";
296
327
  interface PipelineStepRun {
297
328
  stepId: string;
298
329
  status: TaskStatus;
@@ -1823,6 +1854,12 @@ interface CreateTaskInput {
1823
1854
  agent?: string;
1824
1855
  context?: TaskContext;
1825
1856
  }
1857
+ interface RunTaskOptions {
1858
+ /** Total attempts (1 = no automatic retry). Retries apply to retryable errors only. */
1859
+ maxAttempts?: number;
1860
+ /** Linear backoff base in ms: waits backoffMs × attempt between attempts. */
1861
+ retryBackoffMs?: number;
1862
+ }
1826
1863
  interface TaskEngineOptions {
1827
1864
  agents: AgentRegistry;
1828
1865
  roles: RoleRegistry;
@@ -1840,10 +1877,12 @@ declare class TaskEngine {
1840
1877
  get store(): TaskStore;
1841
1878
  create(input: CreateTaskInput): Promise<Task>;
1842
1879
  queue(id: string): Promise<Task>;
1843
- /** Runs the task through its assigned agent and records the result. */
1844
- run(id: string): Promise<Task>;
1880
+ /** Runs the task through its assigned agent and records the result.
1881
+ * `maxAttempts` enables bounded automatic retries of *retryable* errors
1882
+ * with linear backoff; unknown/timed_out outcomes are never auto-retried. */
1883
+ run(id: string, options?: RunTaskOptions): Promise<Task>;
1845
1884
  cancel(id: string): Promise<Task>;
1846
- /** Error recovery (§27 Phase 9): reset a failed/cancelled task so it can run again. */
1885
+ /** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
1847
1886
  retry(id: string): Promise<Task>;
1848
1887
  get(id: string): Promise<Task | undefined>;
1849
1888
  list(filter?: TaskStoreFilter): Promise<Task[]>;
@@ -2111,6 +2150,6 @@ declare class ProjectStores {
2111
2150
  constructor(directory?: string);
2112
2151
  }
2113
2152
 
2114
- declare const OMNINODE_VERSION = "2.0.0-alpha.1";
2153
+ declare const OMNINODE_VERSION = "2.0.0-alpha.2";
2115
2154
 
2116
- export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type AuditAction, type AuditEvent, type AuditSink, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type DiscoveryResult, type EnvelopeMeta, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryQuery, type MemoryScope, MemoryService, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunFilter, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type Plan, type PlanFilter, type PlanRequest, type PlanStep, type PlanStore, type PlannerConfigYaml, PlannerError, ProcessAgent, type Project, type ProjectConfigYaml, ProjectStores, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, defaultProjectConfigYaml, expandEnvRefs, extractReportFromText, findConfigFile, isOmniNodeError, keywords, loadConfig, logger, parsePlanJson, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema, similar };
2155
+ export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type AuditAction, type AuditEvent, type AuditSink, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type DiscoveryResult, type EnvelopeMeta, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryQuery, type MemoryScope, MemoryService, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunFilter, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type Plan, type PlanFilter, type PlanRequest, type PlanStep, type PlanStore, type PlannerConfigYaml, PlannerError, ProcessAgent, type Project, type ProjectConfigYaml, ProjectStores, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, defaultProjectConfigYaml, expandEnvRefs, extractReportFromText, findConfigFile, isOmniNodeError, keywords, loadConfig, logger, parsePlanJson, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema, similar };
package/dist/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ // src/types/task.ts
2
+ var TERMINAL_TASK_STATUSES = [
3
+ "completed",
4
+ "failed",
5
+ "timed_out",
6
+ "cancelled",
7
+ "partially_completed"
8
+ ];
9
+
1
10
  // src/errors/index.ts
2
11
  var OmniNodeError = class extends Error {
3
12
  code;
@@ -804,7 +813,19 @@ var ProcessAgent = class {
804
813
  new AgentError(
805
814
  "AGENT_TIMEOUT",
806
815
  `Agent "${this.config.name}" timed out after ${timeoutMs}ms and was killed.`,
807
- { details: { taskId: input.taskId, stdout: stdout.slice(0, 4e3), stderr: stderr.slice(0, 2e3) } }
816
+ {
817
+ details: {
818
+ operation: "agent.run",
819
+ agent: this.config.name,
820
+ durationMs: timeoutMs,
821
+ // The process was started and may have performed side
822
+ // effects before being killed — the outcome is unknown,
823
+ // not failed (roadmap §6.3).
824
+ dispatched: true,
825
+ stdout: stdout.slice(0, 4e3),
826
+ stderr: stderr.slice(0, 2e3)
827
+ }
828
+ }
808
829
  )
809
830
  );
810
831
  return;
@@ -1082,12 +1103,18 @@ import { randomBytes } from "crypto";
1082
1103
  var ALLOWED_TRANSITIONS = {
1083
1104
  created: ["queued", "running", "cancelled"],
1084
1105
  queued: ["running", "cancelled"],
1085
- running: ["completed", "failed", "cancelled"],
1106
+ running: ["completed", "failed", "timed_out", "unknown", "cancelled", "partially_completed"],
1086
1107
  waiting: ["running", "cancelled"],
1087
1108
  completed: [],
1088
- failed: [],
1089
- cancelled: []
1109
+ // failed → running is the only re-entry: bounded automatic retry (§6.4).
1110
+ failed: ["running"],
1111
+ timed_out: [],
1112
+ cancelled: [],
1113
+ unknown: [],
1114
+ partially_completed: []
1090
1115
  };
1116
+ var RETRYABLE_ERROR_CODES = ["AGENT_FAILED"];
1117
+ var EXECUTION_HISTORY_LIMIT = 10;
1091
1118
  var TaskEngine = class {
1092
1119
  constructor(options) {
1093
1120
  this.options = options;
@@ -1119,6 +1146,7 @@ var TaskEngine = class {
1119
1146
  id: generateTaskId(),
1120
1147
  objective: input.objective.trim(),
1121
1148
  status: "created",
1149
+ attempt: 0,
1122
1150
  createdAt: now,
1123
1151
  updatedAt: now,
1124
1152
  ...input.project !== void 0 ? { project: input.project } : {},
@@ -1135,8 +1163,10 @@ var TaskEngine = class {
1135
1163
  const task = await this.mustGet(id);
1136
1164
  return this.transition(task, "queued");
1137
1165
  }
1138
- /** Runs the task through its assigned agent and records the result. */
1139
- async run(id) {
1166
+ /** Runs the task through its assigned agent and records the result.
1167
+ * `maxAttempts` enables bounded automatic retries of *retryable* errors
1168
+ * with linear backoff; unknown/timed_out outcomes are never auto-retried. */
1169
+ async run(id, options = {}) {
1140
1170
  const task = await this.mustGet(id);
1141
1171
  if (task.status !== "created" && task.status !== "queued") {
1142
1172
  throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
@@ -1149,49 +1179,84 @@ var TaskEngine = class {
1149
1179
  throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
1150
1180
  }
1151
1181
  const role = task.role ? this.options.roles.get(task.role) : void 0;
1152
- const running = await this.transition(task, "running");
1153
1182
  this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
1154
- let background = running.context?.background;
1183
+ let background = task.context?.background;
1155
1184
  if (this.options.memory) {
1156
1185
  const memoryContext = await this.options.memory.gatherContext({
1157
- objective: running.objective,
1158
- ...running.role ? { role: running.role } : {},
1159
- ...running.project ? { project: running.project } : {}
1186
+ objective: task.objective,
1187
+ ...task.role ? { role: task.role } : {},
1188
+ ...task.project ? { project: task.project } : {}
1160
1189
  });
1161
1190
  if (memoryContext.length > 0) {
1162
1191
  background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
1163
1192
  }
1164
1193
  }
1165
- let output;
1166
- try {
1167
- output = await agent.run({
1168
- taskId: running.id,
1169
- objective: running.objective,
1170
- ...role ? { role } : {},
1171
- ...background !== void 0 ? { context: background } : {}
1172
- });
1173
- } catch (error) {
1174
- const message = error instanceof Error ? error.message : String(error);
1175
- this.log.error(`Task ${task.id} failed: ${message}`);
1176
- return this.transition(running, "failed", {
1177
- result: { error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }
1194
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 1);
1195
+ const backoffMs = options.retryBackoffMs ?? 0;
1196
+ let current = task;
1197
+ for (let i = 0; i < maxAttempts; i += 1) {
1198
+ const attempt = current.attempt + 1;
1199
+ const executionId = `exec-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1200
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1201
+ current = await this.transition(current, "running", { attempt });
1202
+ let output;
1203
+ let thrown;
1204
+ try {
1205
+ output = await agent.run({
1206
+ taskId: current.id,
1207
+ objective: current.objective,
1208
+ ...role ? { role } : {},
1209
+ ...background !== void 0 ? { context: background } : {}
1210
+ });
1211
+ } catch (error) {
1212
+ thrown = error;
1213
+ }
1214
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
1215
+ if (thrown !== void 0 || output === void 0) {
1216
+ const outcome = classifyThrown(thrown);
1217
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
1218
+ const retryable = outcome === "failed" && RETRYABLE_ERROR_CODES.includes(errorCode(thrown)) && i < maxAttempts - 1;
1219
+ current = await this.transition(current, outcome, {
1220
+ attempt,
1221
+ lastError: message,
1222
+ execution: {
1223
+ executionId,
1224
+ attempt,
1225
+ startedAt,
1226
+ finishedAt,
1227
+ outcome,
1228
+ error: message
1229
+ }
1230
+ });
1231
+ this.log.warn(`Task ${current.id} attempt ${attempt} \u2192 ${outcome}: ${message}`);
1232
+ if (retryable) {
1233
+ if (backoffMs > 0) await sleep(backoffMs * attempt);
1234
+ continue;
1235
+ }
1236
+ if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
1237
+ return current;
1238
+ }
1239
+ const result = {
1240
+ ...output.summary !== void 0 ? { summary: output.summary } : {},
1241
+ ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
1242
+ // Keep the verbatim output (bounded) for report extraction and audit.
1243
+ ...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
1244
+ ...output.error !== void 0 ? { error: output.error } : {},
1245
+ finishedAt
1246
+ };
1247
+ const finalStatus = output.status === "completed" ? "completed" : "failed";
1248
+ current = await this.transition(current, finalStatus, {
1249
+ attempt,
1250
+ result,
1251
+ execution: { executionId, attempt, startedAt, finishedAt, outcome: finalStatus }
1178
1252
  });
1253
+ this.log.info(`Task ${current.id} finished: ${finalStatus}.`);
1254
+ break;
1179
1255
  }
1180
- const result = {
1181
- ...output.summary !== void 0 ? { summary: output.summary } : {},
1182
- ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
1183
- // Keep the verbatim output (bounded) for report extraction and audit.
1184
- ...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
1185
- ...output.error !== void 0 ? { error: output.error } : {},
1186
- finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1187
- };
1188
- const finalStatus = output.status === "completed" ? "completed" : "failed";
1189
- const finished = await this.transition(running, finalStatus, { result });
1190
- this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
1191
1256
  if (this.options.memory) {
1192
- await this.options.memory.recordTaskOutcome(finished);
1257
+ await this.options.memory.recordTaskOutcome(current);
1193
1258
  }
1194
- return finished;
1259
+ return current;
1195
1260
  }
1196
1261
  async cancel(id) {
1197
1262
  const task = await this.mustGet(id);
@@ -1204,13 +1269,14 @@ var TaskEngine = class {
1204
1269
  }
1205
1270
  return this.transition(task, "cancelled");
1206
1271
  }
1207
- /** Error recovery (§27 Phase 9): reset a failed/cancelled task so it can run again. */
1272
+ /** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
1208
1273
  async retry(id) {
1209
1274
  const task = await this.mustGet(id);
1210
- if (task.status !== "failed" && task.status !== "cancelled") {
1275
+ const retryableStates = ["failed", "cancelled", "unknown", "timed_out"];
1276
+ if (!retryableStates.includes(task.status)) {
1211
1277
  throw new TaskError(
1212
1278
  "TASK_INVALID",
1213
- `Only failed or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
1279
+ `Only failed, timed out, unknown or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
1214
1280
  );
1215
1281
  }
1216
1282
  const { result: _discarded, ...rest } = task;
@@ -1242,10 +1308,14 @@ var TaskEngine = class {
1242
1308
  if (!allowed.includes(to)) {
1243
1309
  throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
1244
1310
  }
1311
+ const executions = extra.execution ? [...task.executions ?? [], extra.execution].slice(-EXECUTION_HISTORY_LIMIT) : task.executions;
1245
1312
  const updated = {
1246
1313
  ...task,
1247
1314
  status: to,
1248
1315
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1316
+ ...extra.attempt !== void 0 ? { attempt: extra.attempt } : {},
1317
+ ...extra.lastError !== void 0 ? { lastError: extra.lastError } : {},
1318
+ ...executions !== void 0 ? { executions } : {},
1249
1319
  ...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
1250
1320
  };
1251
1321
  await this.options.store.save(updated);
@@ -1266,6 +1336,19 @@ var TaskEngine = class {
1266
1336
  function generateTaskId() {
1267
1337
  return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1268
1338
  }
1339
+ function errorCode(error) {
1340
+ return error?.code ?? "";
1341
+ }
1342
+ function classifyThrown(error) {
1343
+ if (error instanceof AgentError && error.code === "AGENT_TIMEOUT") {
1344
+ const dispatched = error.details?.dispatched;
1345
+ return dispatched === false ? "timed_out" : "unknown";
1346
+ }
1347
+ return "failed";
1348
+ }
1349
+ function sleep(ms) {
1350
+ return new Promise((resolve) => setTimeout(resolve, ms));
1351
+ }
1269
1352
 
1270
1353
  // src/audit/index.ts
1271
1354
  import { appendFile, mkdir as mkdir2 } from "fs/promises";
@@ -1974,7 +2057,7 @@ ${contribution}`);
1974
2057
  }
1975
2058
  const stepRun = {
1976
2059
  stepId: step.id,
1977
- status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
2060
+ status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
1978
2061
  ...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
1979
2062
  ...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
1980
2063
  ...outcome.error !== void 0 ? { error: outcome.error } : {}
@@ -1985,8 +2068,10 @@ ${contribution}`);
1985
2068
  run.reports = [...allReports];
1986
2069
  await this.options.store.save(run);
1987
2070
  }
1988
- const failed = [...outcomes.values()].some((o) => o.status === "failed");
1989
- const finalStatus = failed ? "failed" : "completed";
2071
+ const allOutcomes = [...outcomes.values()];
2072
+ const failed = allOutcomes.some((o) => o.status === "failed");
2073
+ const partial = !failed && allOutcomes.some((o) => o.status === "partial");
2074
+ const finalStatus = failed ? "failed" : partial ? "partial" : "completed";
1990
2075
  const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
1991
2076
  let resultSummary;
1992
2077
  for (let i = def.steps.length - 1; i >= 0; i -= 1) {
@@ -2092,6 +2177,7 @@ ${contribution}`);
2092
2177
  )
2093
2178
  );
2094
2179
  const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
2180
+ const completedTasks = results.filter((r) => r.status === "completed");
2095
2181
  const failedTasks = results.filter((r) => r.status !== "completed");
2096
2182
  const lines = results.map((result, index) => {
2097
2183
  const agentName = agentNames[index] ?? "unknown";
@@ -2101,12 +2187,15 @@ ${contribution}`);
2101
2187
  const reports = results.flatMap((r) => r.result?.reports ?? []);
2102
2188
  const summary = lines.join("\n");
2103
2189
  if (failedTasks.length > 0) {
2104
- const firstError = failedTasks[0]?.result?.error ?? "unknown error";
2190
+ const firstError = failedTasks[0]?.result?.error ?? failedTasks[0]?.lastError ?? "unknown outcome";
2191
+ const states = failedTasks.map((r) => r.status).join(", ");
2105
2192
  return {
2106
- status: "failed",
2193
+ // Some agents succeeded → the step produced usable but incomplete
2194
+ // intelligence: partial, not failed (roadmap §9.3).
2195
+ status: completedTasks.length > 0 ? "partial" : "failed",
2107
2196
  summary,
2108
2197
  taskIds: created.map((t) => t.id),
2109
- error: `${failedTasks.length}/${results.length} task(s) failed \u2014 ${firstError}`,
2198
+ error: `${failedTasks.length}/${results.length} task(s) incomplete (${states}) \u2014 ${firstError}`,
2110
2199
  reports
2111
2200
  };
2112
2201
  }
@@ -2122,7 +2211,9 @@ ${contribution}`);
2122
2211
  const result = await this.options.tasks.run(task.id);
2123
2212
  if (result.status !== "completed") {
2124
2213
  return {
2125
- status: "failed",
2214
+ // Unknown/timed_out outcomes are not failures — the work may have
2215
+ // happened remotely; the step is incomplete, not broken.
2216
+ status: result.status === "unknown" || result.status === "timed_out" ? "partial" : "failed",
2126
2217
  summary: result.result?.summary ?? "",
2127
2218
  taskIds: [task.id],
2128
2219
  error: result.result?.error ?? `task ${task.id} did not complete`,
@@ -2594,7 +2685,7 @@ var ProjectStores = class {
2594
2685
  };
2595
2686
 
2596
2687
  // src/version.ts
2597
- var OMNINODE_VERSION = "2.0.0-alpha.1";
2688
+ var OMNINODE_VERSION = "2.0.0-alpha.2";
2598
2689
  export {
2599
2690
  AgentError,
2600
2691
  AgentRegistry,
@@ -2628,6 +2719,7 @@ export {
2628
2719
  RememberaMemoryProvider,
2629
2720
  ReportService,
2630
2721
  RoleRegistry,
2722
+ TERMINAL_TASK_STATUSES,
2631
2723
  TaskEngine,
2632
2724
  TaskError,
2633
2725
  agentConfigSchema,