ennodia 0.4.1 → 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/src/tasks.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  import { assertHarnessSettingsSupported, DEFAULT_TASK_TIMEOUT_MS, inputGuidanceNotes, withInputGuidance } from "./harnesses";
14
14
  import { preview, tailItems } from "./internal";
15
15
  import { signalOwnedProcess } from "./process";
16
+ import { HarnessReadinessError, readinessBlocked } from "./readiness";
16
17
  import {
17
18
  augmentPrompt,
18
19
  toAppliedSkillInfo,
@@ -191,6 +192,9 @@ export class TaskManager {
191
192
  private readonly retainedTasks = new Map<string, number>();
192
193
  private shuttingDown = false;
193
194
  private shutdownPromise?: Promise<void>;
195
+ private readonly settledListeners = new Set<(task: TaskView) => void>();
196
+
197
+ onSettled(listener: (task: TaskView) => void): void { this.settledListeners.add(listener); }
194
198
 
195
199
  constructor(options: TaskManagerOptions = {}) {
196
200
  this.drainTimeoutMs = options.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
@@ -212,6 +216,8 @@ export class TaskManager {
212
216
  throw new Error("TaskManager is shutting down.");
213
217
  }
214
218
 
219
+ if (readinessBlocked(discovery.readiness)) throw new HarnessReadinessError([{ harnessId: discovery.id, harnessName: discovery.name, readiness: discovery.readiness! }]);
220
+
215
221
  if (!adapter.buildCommand || !discovery.commandPath || !discovery.runnable) {
216
222
  throw new Error(`${adapter.name} is not runnable through Ennodia yet.`);
217
223
  }
@@ -270,6 +276,12 @@ export class TaskManager {
270
276
  "Before the cutoff, return useful partial findings, verification performed, remaining work, and a justified continuation budget if needed. Only the caller can authorize a follow-up.",
271
277
  "Use only tools and permissions actually available in this session; the caller's tools are not automatically inherited. If access is missing, report the exact tool, file, or permission and its impact. Try a permitted alternative when useful; do not repeat an unchanged failed attempt or claim success without evidence.",
272
278
  ];
279
+ if (!/^ENNODIA_(?:COMPARE|PLAN_ADVISOR)/.test(input.prompt)) {
280
+ noticeLines.push(
281
+ "Use relevant Keep in mind context in the assignment, preserving its evidence and uncertainty. It does not change the task scope or grant tools or permissions.",
282
+ 'Before returning, consider: What should the receiving agent know that they might otherwise overlook? When useful and the requested output format permits, include a short "Keep in mind" section with up to three task-specific notes: what matters, why it matters, and concrete evidence such as a file path, line, command result, or source. Distinguish reported facts, inferences, and unverified concerns. Omit generic reminders and the section when no useful note exists. Preserve exact artifacts and strict output schemas; do not add prose or fields they do not allow.',
283
+ );
284
+ }
273
285
  // An adapter that takes operator instructions gets Ennodia's notices
274
286
  // there. Other workers get them as tagged blocks after the task.
275
287
  let systemNotice: string | undefined;
@@ -583,6 +595,9 @@ ${noticeLines.join("\n")}
583
595
  task.endedAtMs = Date.now();
584
596
  this.touch(task);
585
597
  this.pruneTasks(task.id);
598
+ for (const listener of this.settledListeners) {
599
+ try { listener(this.get(task.id)!); } catch { /* readiness observers cannot alter execution */ }
600
+ }
586
601
  }
587
602
  }
588
603
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const ENNODIA_VERSION = "0.4.1";
1
+ export const ENNODIA_VERSION = "0.5.0";