@hilbras/omninode 2.0.0-alpha.4 → 2.0.0-alpha.5

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
@@ -443,18 +443,37 @@ interface PipelineDefinition {
443
443
  * authoritative detail always lives in `stepRuns`/`taskRuns`.
444
444
  */
445
445
  type PipelineRunStatus = "pending" | "running" | "completed" | "failed" | "partial" | "cancelled";
446
+ /**
447
+ * One step execution inside a run (§9.1 — Step Run is NOT the Step).
448
+ */
446
449
  interface PipelineStepRun {
447
450
  stepId: string;
448
451
  status: TaskStatus;
449
452
  /** Tasks created for this step (research fans out to several). */
450
453
  taskIds?: string[];
454
+ /** Execution ids of the tasks this step created, for correlation (§6.5). */
455
+ executionIds?: string[];
456
+ /** Which attempt of this step produced this record (1 = first try). */
457
+ attempt?: number;
451
458
  /** Set on plan steps that produced a stored plan (§18). */
452
459
  planId?: string;
453
460
  error?: string;
454
461
  }
462
+ /** One execution attempt of a run (§9.1 — Attempt is not the Run). */
463
+ interface PipelineRunAttempt {
464
+ attempt: number;
465
+ startedAt: string;
466
+ finishedAt?: string;
467
+ status: PipelineRunStatus;
468
+ }
455
469
  interface PipelineRun {
456
470
  id: string;
457
471
  pipelineId: string;
472
+ /** Execution attempt counter: retries of the same run increment it. */
473
+ attempt: number;
474
+ /** Set when cancellation was requested; the run stops at the next check point. */
475
+ cancellationRequested?: boolean;
476
+ cancellationRequestedAt?: string;
458
477
  /** The objective this run is executing (persisted for retries). */
459
478
  objective?: string;
460
479
  status: PipelineRunStatus;
@@ -469,6 +488,28 @@ interface PipelineRun {
469
488
  planId?: string;
470
489
  /** Outcome summary of the final executed step (§28 — Result). */
471
490
  resultSummary?: string;
491
+ /** Per-attempt history (§9.1). */
492
+ attempts?: PipelineRunAttempt[];
493
+ }
494
+ /**
495
+ * Executor contract (roadmap §9.1 / §9.2): a Pipeline definition, a Run of
496
+ * it, Steps, Step Runs and Attempts are distinct concepts; this interface is
497
+ * what orchestration code depends on.
498
+ */
499
+ interface IPipelineExecutor {
500
+ validate(definition: PipelineDefinition): void;
501
+ run(definition: PipelineDefinition, options?: PipelineRunOptions): Promise<PipelineRun>;
502
+ cancel(runId: string): Promise<void>;
503
+ get(runId: string): Promise<PipelineRun | undefined>;
504
+ listRuns(filter?: {
505
+ pipelineId?: string;
506
+ }): Promise<PipelineRun[]>;
507
+ }
508
+ interface PipelineRunOptions {
509
+ objective?: string;
510
+ onStep?: (stepRun: PipelineStepRun) => void;
511
+ /** Total run attempts (1 = no automatic rerun of the whole run). */
512
+ maxAttempts?: number;
472
513
  }
473
514
 
474
515
  /** Plan types: the final implementation plan produced by the planner layer (§18). */
@@ -2458,11 +2499,19 @@ interface RunPipelineOptions {
2458
2499
  objective?: string;
2459
2500
  onStep?: (stepRun: PipelineStepRun) => void;
2460
2501
  }
2461
- declare class PipelineEngine {
2502
+ declare class PipelineEngine implements IPipelineExecutor {
2462
2503
  private readonly options;
2463
2504
  private readonly log;
2505
+ /** Runs executing in THIS process — cancellation can terminate them directly. */
2506
+ private readonly activeRuns;
2464
2507
  constructor(options: PipelineEngineOptions);
2465
2508
  get store(): PipelineRunStore;
2509
+ /**
2510
+ * Saves the run while preserving fields that may be set by another process
2511
+ * (e.g. a cancellation request) — an in-memory snapshot must never clobber
2512
+ * externally requested cancellation (§9.2).
2513
+ */
2514
+ private persist;
2466
2515
  /** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
2467
2516
  validate(def: PipelineDefinition): void;
2468
2517
  private assertAcyclic;
@@ -2471,13 +2520,18 @@ declare class PipelineEngine {
2471
2520
  * dependencies, which enable parallel branches.
2472
2521
  */
2473
2522
  private effectiveDependencies;
2474
- run(def: PipelineDefinition, options?: RunPipelineOptions): Promise<PipelineRun>;
2523
+ run(def: PipelineDefinition, options?: PipelineRunOptions): Promise<PipelineRun>;
2524
+ /** First-class cancellation (§9.2): requests cancellation and terminates
2525
+ * tasks that are running in this process immediately. */
2526
+ cancel(runId: string): Promise<void>;
2475
2527
  get(runId: string): Promise<PipelineRun | undefined>;
2476
2528
  listRuns(filter?: {
2477
2529
  pipelineId?: string;
2478
2530
  }): Promise<PipelineRun[]>;
2479
2531
  private shouldRun;
2480
2532
  private executeStep;
2533
+ /** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
2534
+ private trackLiveTasks;
2481
2535
  private executeStepOnce;
2482
2536
  /** Research fan-out: one task per agent, in parallel (§15). */
2483
2537
  private runFanOut;
@@ -2509,6 +2563,6 @@ declare class ProjectStores {
2509
2563
  constructor(directory?: string);
2510
2564
  }
2511
2565
 
2512
- declare const OMNINODE_VERSION = "2.0.0-alpha.4";
2566
+ declare const OMNINODE_VERSION = "2.0.0-alpha.5";
2513
2567
 
2514
- export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, 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 ArtifactKind, type ArtifactPayload, type AskedQuestion, 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 CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type EnvPolicy, type EnvelopeMeta, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_MESSAGE_BYTES, 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, PROTOCOL_V2, 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, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SUPPORTED_PROTOCOL_VERSIONS, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultProjectConfigYaml, describeExit, encodeMessage, envelope, expandEnvRefs, extractReportFromText, findConfigFile, isOmniNodeError, isSupportedProtocol, keywords, killTree, loadConfig, logger, newMessageId, parseHello, parsePlanJson, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, similar, validateEnvelope, validateWorkingDirectory };
2568
+ export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, 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 ArtifactKind, type ArtifactPayload, type AskedQuestion, 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 CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type EnvPolicy, type EnvelopeMeta, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPipelineExecutor, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_MESSAGE_BYTES, 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, PROTOCOL_V2, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunAttempt, type PipelineRunFilter, type PipelineRunOptions, 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, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SUPPORTED_PROTOCOL_VERSIONS, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultProjectConfigYaml, describeExit, encodeMessage, envelope, expandEnvRefs, extractReportFromText, findConfigFile, isOmniNodeError, isSupportedProtocol, keywords, killTree, loadConfig, logger, newMessageId, parseHello, parsePlanJson, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, similar, validateEnvelope, validateWorkingDirectory };
package/dist/index.js CHANGED
@@ -1430,7 +1430,7 @@ function describeExit(code, signal) {
1430
1430
  return `Process exited with code ${code}.`;
1431
1431
  }
1432
1432
  function sanitizeChunk(chunk) {
1433
- return chunk.toString("utf8").replace(/\u0000/g, "");
1433
+ return [...chunk.toString("utf8")].filter((char) => char.charCodeAt(0) !== 0).join("");
1434
1434
  }
1435
1435
  function normalizeReport(payload, taskId, agentName, index) {
1436
1436
  if (payload === null || typeof payload !== "object") return void 0;
@@ -1777,6 +1777,11 @@ var TaskEngine = class {
1777
1777
  if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
1778
1778
  return current;
1779
1779
  }
1780
+ const currentState = await this.options.store.get(current.id);
1781
+ if (currentState?.status === "cancelled") {
1782
+ current = currentState;
1783
+ break;
1784
+ }
1780
1785
  const result = {
1781
1786
  ...output.summary !== void 0 ? { summary: output.summary } : {},
1782
1787
  ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
@@ -2439,9 +2444,23 @@ var PipelineEngine = class {
2439
2444
  }
2440
2445
  options;
2441
2446
  log;
2447
+ /** Runs executing in THIS process — cancellation can terminate them directly. */
2448
+ activeRuns = /* @__PURE__ */ new Map();
2442
2449
  get store() {
2443
2450
  return this.options.store;
2444
2451
  }
2452
+ /**
2453
+ * Saves the run while preserving fields that may be set by another process
2454
+ * (e.g. a cancellation request) — an in-memory snapshot must never clobber
2455
+ * externally requested cancellation (§9.2).
2456
+ */
2457
+ async persist(run) {
2458
+ const current = await this.options.store.get(run.id);
2459
+ await this.options.store.save({
2460
+ ...run,
2461
+ ...current?.cancellationRequested === true ? { cancellationRequested: true, cancellationRequestedAt: current.cancellationRequestedAt } : {}
2462
+ });
2463
+ }
2445
2464
  /** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
2446
2465
  validate(def) {
2447
2466
  const ids = /* @__PURE__ */ new Set();
@@ -2541,18 +2560,44 @@ var PipelineEngine = class {
2541
2560
  );
2542
2561
  }
2543
2562
  }
2563
+ if (step.model) {
2564
+ const separator = step.model.indexOf(":");
2565
+ if (separator <= 0 || separator === step.model.length - 1) {
2566
+ throw new PipelineError(
2567
+ "PIPELINE_INVALID",
2568
+ `Step "${step.id}" has an invalid model reference "${step.model}" \u2014 use "provider:model-id".`
2569
+ );
2570
+ }
2571
+ const providerName = step.model.slice(0, separator);
2572
+ if (!this.options.providers.some((p) => p.name === providerName)) {
2573
+ throw new PipelineError(
2574
+ "PIPELINE_INVALID",
2575
+ `Step "${step.id}" uses model "${step.model}" but provider "${providerName}" is not configured.`
2576
+ );
2577
+ }
2578
+ }
2544
2579
  }
2545
2580
  const now = (/* @__PURE__ */ new Date()).toISOString();
2546
2581
  const run = {
2547
2582
  id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
2548
2583
  pipelineId: def.id,
2584
+ attempt: 1,
2549
2585
  objective,
2550
2586
  status: "running",
2551
2587
  startedAt: now,
2552
2588
  stepRuns: []
2553
2589
  };
2554
2590
  await this.options.store.save(run);
2555
- this.log.info(`Running pipeline ${def.id} as ${run.id}.`);
2591
+ const liveTaskIds = /* @__PURE__ */ new Set();
2592
+ this.activeRuns.set(run.id, {
2593
+ taskIds: liveTaskIds,
2594
+ cancelTasks: async () => {
2595
+ for (const taskId of liveTaskIds) {
2596
+ await this.options.tasks.cancel(taskId).catch(() => void 0);
2597
+ }
2598
+ }
2599
+ });
2600
+ this.log.info(`Running pipeline ${def.id} as ${run.id} (attempt ${run.attempt}).`);
2556
2601
  await this.options.audit?.record({
2557
2602
  at: (/* @__PURE__ */ new Date()).toISOString(),
2558
2603
  action: "pipeline.run.started",
@@ -2564,7 +2609,22 @@ var PipelineEngine = class {
2564
2609
  const pending = new Set(def.steps.map((s) => s.id));
2565
2610
  const contextParts = [];
2566
2611
  const allReports = [];
2612
+ let cancelled = false;
2567
2613
  while (pending.size > 0) {
2614
+ const persisted = await this.options.store.get(run.id);
2615
+ if (persisted?.cancellationRequested === true) {
2616
+ cancelled = true;
2617
+ run.cancellationRequested = true;
2618
+ run.cancellationRequestedAt = persisted.cancellationRequestedAt;
2619
+ await this.activeRuns.get(run.id)?.cancelTasks();
2620
+ for (const step of def.steps.filter((s) => pending.has(s.id))) {
2621
+ const stepRun = { stepId: step.id, status: "cancelled" };
2622
+ run.stepRuns.push(stepRun);
2623
+ options.onStep?.(stepRun);
2624
+ }
2625
+ pending.clear();
2626
+ break;
2627
+ }
2568
2628
  const ready = def.steps.filter(
2569
2629
  (step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
2570
2630
  );
@@ -2581,7 +2641,8 @@ var PipelineEngine = class {
2581
2641
  ...role ? { role } : {},
2582
2642
  combinedContext: contextParts.join("\n\n"),
2583
2643
  reports: [...allReports],
2584
- pipelineRunId: run.id
2644
+ pipelineRunId: run.id,
2645
+ runId: run.id
2585
2646
  };
2586
2647
  return this.executeStep(step, ctx);
2587
2648
  })
@@ -2598,6 +2659,8 @@ ${contribution}`);
2598
2659
  }
2599
2660
  const stepRun = {
2600
2661
  stepId: step.id,
2662
+ attempt: outcome.attempt ?? 1,
2663
+ ...outcome.executionIds && outcome.executionIds.length > 0 ? { executionIds: outcome.executionIds } : {},
2601
2664
  status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
2602
2665
  ...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
2603
2666
  ...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
@@ -2607,12 +2670,12 @@ ${contribution}`);
2607
2670
  options.onStep?.(stepRun);
2608
2671
  });
2609
2672
  run.reports = [...allReports];
2610
- await this.options.store.save(run);
2673
+ await this.persist(run);
2611
2674
  }
2612
2675
  const allOutcomes = [...outcomes.values()];
2613
- const failed = allOutcomes.some((o) => o.status === "failed");
2614
- const partial = !failed && allOutcomes.some((o) => o.status === "partial");
2615
- const finalStatus = failed ? "failed" : partial ? "partial" : "completed";
2676
+ const failed = !cancelled && allOutcomes.some((o) => o.status === "failed");
2677
+ const partial = !cancelled && !failed && allOutcomes.some((o) => o.status === "partial");
2678
+ const finalStatus = cancelled ? "cancelled" : failed ? "failed" : partial ? "partial" : "completed";
2616
2679
  const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
2617
2680
  let resultSummary;
2618
2681
  for (let i = def.steps.length - 1; i >= 0; i -= 1) {
@@ -2640,16 +2703,19 @@ ${contribution}`);
2640
2703
  run.reports = collected;
2641
2704
  }
2642
2705
  }
2706
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2707
+ this.activeRuns.delete(run.id);
2643
2708
  const finished = {
2644
2709
  ...run,
2645
2710
  status: finalStatus,
2646
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
2711
+ finishedAt,
2712
+ attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
2647
2713
  ...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
2648
2714
  ...combinedReportId !== void 0 ? { combinedReportId } : {},
2649
2715
  ...planId !== void 0 ? { planId } : {},
2650
2716
  ...resultSummary !== void 0 ? { resultSummary } : {}
2651
2717
  };
2652
- await this.options.store.save(finished);
2718
+ await this.persist(finished);
2653
2719
  this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
2654
2720
  await this.options.audit?.record({
2655
2721
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2659,6 +2725,23 @@ ${contribution}`);
2659
2725
  });
2660
2726
  return finished;
2661
2727
  }
2728
+ /** First-class cancellation (§9.2): requests cancellation and terminates
2729
+ * tasks that are running in this process immediately. */
2730
+ async cancel(runId) {
2731
+ const run = await this.options.store.get(runId);
2732
+ if (!run) {
2733
+ throw new PipelineError("PIPELINE_NOT_FOUND", `Pipeline run "${runId}" does not exist.`);
2734
+ }
2735
+ await this.options.store.save({
2736
+ ...run,
2737
+ cancellationRequested: true,
2738
+ cancellationRequestedAt: (/* @__PURE__ */ new Date()).toISOString()
2739
+ });
2740
+ const active = this.activeRuns.get(runId);
2741
+ if (active) {
2742
+ await active.cancelTasks();
2743
+ }
2744
+ }
2662
2745
  async get(runId) {
2663
2746
  return this.options.store.get(runId);
2664
2747
  }
@@ -2682,9 +2765,16 @@ ${contribution}`);
2682
2765
  this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
2683
2766
  }
2684
2767
  last = await this.executeStepOnce(step, ctx);
2685
- if (last.status !== "failed") return last;
2768
+ if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
2686
2769
  }
2687
- return last;
2770
+ return { ...last, attempt: retries + 1 };
2771
+ }
2772
+ /** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
2773
+ trackLiveTasks(runId, taskIds) {
2774
+ if (!runId) return;
2775
+ const active = this.activeRuns.get(runId);
2776
+ if (!active) return;
2777
+ for (const id of taskIds) active.taskIds.add(id);
2688
2778
  }
2689
2779
  async executeStepOnce(step, ctx) {
2690
2780
  switch (step.kind) {
@@ -2717,7 +2807,9 @@ ${contribution}`);
2717
2807
  })
2718
2808
  )
2719
2809
  );
2810
+ this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
2720
2811
  const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
2812
+ const executionIds = results.map((r) => r.executions?.at(-1)?.executionId).filter((id) => id !== void 0);
2721
2813
  const completedTasks = results.filter((r) => r.status === "completed");
2722
2814
  const failedTasks = results.filter((r) => r.status !== "completed");
2723
2815
  const lines = results.map((result, index) => {
@@ -2740,7 +2832,13 @@ ${contribution}`);
2740
2832
  reports
2741
2833
  };
2742
2834
  }
2743
- return { status: "completed", summary, taskIds: created.map((t) => t.id), reports };
2835
+ return {
2836
+ status: "completed",
2837
+ summary,
2838
+ taskIds: created.map((t) => t.id),
2839
+ ...executionIds.length > 0 ? { executionIds } : {},
2840
+ reports
2841
+ };
2744
2842
  }
2745
2843
  async runSingle(step, ctx, agentName) {
2746
2844
  const task = await this.options.tasks.create({
@@ -2749,6 +2847,7 @@ ${contribution}`);
2749
2847
  ...ctx.role ? { role: ctx.role.id } : {},
2750
2848
  ...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
2751
2849
  });
2850
+ this.trackLiveTasks(ctx.runId, [task.id]);
2752
2851
  const result = await this.options.tasks.run(task.id);
2753
2852
  if (result.status !== "completed") {
2754
2853
  return {
@@ -3226,7 +3325,7 @@ var ProjectStores = class {
3226
3325
  };
3227
3326
 
3228
3327
  // src/version.ts
3229
- var OMNINODE_VERSION = "2.0.0-alpha.4";
3328
+ var OMNINODE_VERSION = "2.0.0-alpha.5";
3230
3329
  export {
3231
3330
  AgentAdapterRegistry,
3232
3331
  AgentError,