ennodia 0.1.1 → 0.2.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/CONTRIBUTING.md +60 -36
- package/README.md +74 -58
- package/bench/README.md +7 -5
- package/bench/bug-recall/run.ts +12 -4
- package/bin/ennodia +23 -2
- package/docs/assets/agents/mcp.svg +1 -1
- package/docs/assets/orchestration-pipeline.svg +4 -3
- package/docs/comparisons/agent-frameworks.md +15 -10
- package/docs/comparisons/autogen.md +20 -15
- package/docs/comparisons/chathub.md +15 -14
- package/docs/comparisons/index.md +30 -22
- package/docs/comparisons/langgraph.md +19 -12
- package/docs/comparisons/mixture-of-agents.md +20 -17
- package/docs/comparisons/model-merging.md +7 -5
- package/docs/comparisons/openrouter.md +30 -22
- package/docs/concepts/compositional-audits.md +16 -13
- package/docs/concepts/data-governance.md +31 -15
- package/docs/concepts/how-ennodia-works.md +48 -22
- package/docs/concepts/interfaces-and-core.md +19 -6
- package/docs/concepts/second-opinions.md +20 -20
- package/docs/getting-started.md +55 -27
- package/docs/guides/agent-skills.md +16 -13
- package/docs/guides/budgets-and-limits.md +30 -20
- package/docs/guides/running-better-audits.md +50 -44
- package/docs/index.md +60 -37
- package/docs/install.md +41 -32
- package/docs/reference/benchmarks.md +6 -4
- package/docs/reference/controlled-english.md +130 -0
- package/docs/reference/ennodia-io.md +28 -22
- package/docs/reference/mcp-tools.md +244 -87
- package/docs/reference/supported-harnesses.md +9 -9
- package/package.json +7 -5
- package/src/advisor.ts +685 -0
- package/src/budget.ts +25 -6
- package/src/cli.ts +1 -1
- package/src/compare.ts +248 -52
- package/src/compositional.ts +18 -0
- package/src/core.ts +547 -16
- package/src/harnesses.ts +33 -2
- package/src/index.ts +38 -0
- package/src/plan-advice.ts +601 -0
- package/src/runs.ts +74 -9
- package/src/server.ts +312 -46
- package/src/skills.ts +131 -11
- package/src/tasks.ts +192 -22
- package/src/version.ts +1 -1
package/src/runs.ts
CHANGED
|
@@ -71,12 +71,17 @@ export type RunStartInput = {
|
|
|
71
71
|
mode?: RunMode;
|
|
72
72
|
compare?: RunCompareMode;
|
|
73
73
|
cwd?: string;
|
|
74
|
+
isolateCwd?: boolean;
|
|
74
75
|
model?: string;
|
|
75
76
|
timeoutMs?: number;
|
|
76
77
|
refresh?: boolean;
|
|
77
78
|
judgeHarnessId?: string;
|
|
78
79
|
judgeModel?: string;
|
|
80
|
+
advisorHarnessId?: string;
|
|
81
|
+
advisorModel?: string;
|
|
82
|
+
/** @deprecated Use advisorHarnessId. */
|
|
79
83
|
synthesizerHarnessId?: string;
|
|
84
|
+
/** @deprecated Use advisorModel. */
|
|
80
85
|
synthesizerModel?: string;
|
|
81
86
|
maxOutputChars?: number;
|
|
82
87
|
skills?: Skill[];
|
|
@@ -94,6 +99,11 @@ export type RunView = {
|
|
|
94
99
|
endedAt?: string;
|
|
95
100
|
elapsedMs: number;
|
|
96
101
|
plan: RoutePlan;
|
|
102
|
+
/** True when harnessId was explicitly forced, meaning plan.selected /
|
|
103
|
+
* plan.candidates reflect what the auto-router would have chosen and were
|
|
104
|
+
* NOT what actually ran. Check selectedHarnessIds for the harness(es) that
|
|
105
|
+
* actually executed. */
|
|
106
|
+
harnessOverridden: boolean;
|
|
97
107
|
selectedHarnessIds: string[];
|
|
98
108
|
taskIds: string[];
|
|
99
109
|
compareId?: string;
|
|
@@ -108,6 +118,11 @@ export type RunView = {
|
|
|
108
118
|
eventCount: number;
|
|
109
119
|
events: RunEvent[];
|
|
110
120
|
appliedSkills?: AppliedSkillInfo[];
|
|
121
|
+
/** Skills discoverable in cwd's native skill directories but not part of
|
|
122
|
+
* this run's appliedSkills. A harness may self-select these on its own
|
|
123
|
+
* initiative even though this run didn't request them. Populated by
|
|
124
|
+
* EnnodiaCore.startRun; empty when cwd was not provided. */
|
|
125
|
+
unrequestedSkillsPresent?: string[];
|
|
111
126
|
budget: BudgetCheck;
|
|
112
127
|
};
|
|
113
128
|
|
|
@@ -152,6 +167,7 @@ type InternalRun = {
|
|
|
152
167
|
compareMode: RunCompareMode;
|
|
153
168
|
prompt: string;
|
|
154
169
|
plan: RoutePlan;
|
|
170
|
+
harnessOverridden: boolean;
|
|
155
171
|
selectedHarnessIds: string[];
|
|
156
172
|
taskIds: string[];
|
|
157
173
|
compareId?: string;
|
|
@@ -204,6 +220,8 @@ export class RunManager {
|
|
|
204
220
|
throw new Error("RunManager is shutting down.");
|
|
205
221
|
}
|
|
206
222
|
|
|
223
|
+
validateRunAdvisorAliases(input);
|
|
224
|
+
|
|
207
225
|
const harnesses = await this.dependencies.discoverHarnesses({
|
|
208
226
|
refresh: input.refresh,
|
|
209
227
|
});
|
|
@@ -248,6 +266,7 @@ export class RunManager {
|
|
|
248
266
|
compareMode,
|
|
249
267
|
prompt: input.prompt,
|
|
250
268
|
plan,
|
|
269
|
+
harnessOverridden: Boolean(input.harnessId),
|
|
251
270
|
selectedHarnessIds,
|
|
252
271
|
taskIds: [],
|
|
253
272
|
createdAtMs: now,
|
|
@@ -368,6 +387,7 @@ export class RunManager {
|
|
|
368
387
|
const started = this.dependencies.taskManager.start(adapter, discovery, {
|
|
369
388
|
prompt: input.prompt,
|
|
370
389
|
cwd: input.cwd,
|
|
390
|
+
isolateCwd: input.isolateCwd,
|
|
371
391
|
model: input.model,
|
|
372
392
|
timeoutMs: input.timeoutMs,
|
|
373
393
|
skills: input.skills,
|
|
@@ -466,6 +486,8 @@ export class RunManager {
|
|
|
466
486
|
taskIds,
|
|
467
487
|
judgeHarnessId: input.judgeHarnessId,
|
|
468
488
|
judgeModel: input.judgeModel,
|
|
489
|
+
advisorHarnessId: input.advisorHarnessId,
|
|
490
|
+
advisorModel: input.advisorModel,
|
|
469
491
|
synthesizerHarnessId: input.synthesizerHarnessId,
|
|
470
492
|
synthesizerModel: input.synthesizerModel,
|
|
471
493
|
cwd: input.cwd,
|
|
@@ -485,13 +507,15 @@ export class RunManager {
|
|
|
485
507
|
return;
|
|
486
508
|
}
|
|
487
509
|
|
|
488
|
-
|
|
510
|
+
const finalAnswer = result.advisor?.answer.trim() ||
|
|
511
|
+
result.synthesis?.text.trim();
|
|
512
|
+
if (result.status === "succeeded" && finalAnswer) {
|
|
489
513
|
this.pushEvent(run, {
|
|
490
514
|
type: "compare-succeeded",
|
|
491
515
|
compareId: compare.id,
|
|
492
|
-
message: "Compare returned the
|
|
516
|
+
message: "Compare returned the Advisor recommendation.",
|
|
493
517
|
});
|
|
494
|
-
this.succeedRun(run,
|
|
518
|
+
this.succeedRun(run, finalAnswer);
|
|
495
519
|
return;
|
|
496
520
|
}
|
|
497
521
|
|
|
@@ -566,6 +590,7 @@ export class RunManager {
|
|
|
566
590
|
endedAt: run.endedAtMs ? new Date(run.endedAtMs).toISOString() : undefined,
|
|
567
591
|
elapsedMs: Math.max(0, end - run.createdAtMs),
|
|
568
592
|
plan: run.plan,
|
|
593
|
+
harnessOverridden: run.harnessOverridden,
|
|
569
594
|
selectedHarnessIds: run.selectedHarnessIds,
|
|
570
595
|
taskIds: run.taskIds,
|
|
571
596
|
compareId: run.compareId,
|
|
@@ -865,21 +890,25 @@ function eventFromTask(task: TaskView): Omit<RunEvent, "at"> {
|
|
|
865
890
|
};
|
|
866
891
|
}
|
|
867
892
|
|
|
893
|
+
function taskOutputText(task: TaskView): string {
|
|
894
|
+
return task.finalMessage?.trim() ||
|
|
895
|
+
`${task.stdout.trim()}\n${task.stderr.trim()}`.trim();
|
|
896
|
+
}
|
|
897
|
+
|
|
868
898
|
function hasComparableOutput(task: TaskView): boolean {
|
|
869
|
-
return
|
|
899
|
+
return taskOutputText(task).length > 0;
|
|
870
900
|
}
|
|
871
901
|
|
|
872
902
|
function formatTaskOutputs(tasks: TaskView[]): string {
|
|
873
903
|
if (tasks.length === 1) {
|
|
874
|
-
return
|
|
904
|
+
return taskOutputText(tasks[0]);
|
|
875
905
|
}
|
|
876
906
|
|
|
877
907
|
return tasks
|
|
878
908
|
.map((task) =>
|
|
879
909
|
[
|
|
880
910
|
`## ${task.harnessName} (${task.id})`,
|
|
881
|
-
|
|
882
|
-
"(no output)",
|
|
911
|
+
taskOutputText(task) || "(no output)",
|
|
883
912
|
].join("\n\n")
|
|
884
913
|
)
|
|
885
914
|
.join("\n\n");
|
|
@@ -911,11 +940,47 @@ function compareTaskIds(compare: CompareView | undefined): string[] {
|
|
|
911
940
|
return [];
|
|
912
941
|
}
|
|
913
942
|
|
|
914
|
-
return [
|
|
915
|
-
|
|
943
|
+
return [
|
|
944
|
+
...new Set([
|
|
945
|
+
compare.judgeTaskId,
|
|
946
|
+
compare.advisorTaskId,
|
|
947
|
+
compare.synthesizerTaskId,
|
|
948
|
+
].filter((taskId): taskId is string => Boolean(taskId))),
|
|
949
|
+
];
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function validateRunAdvisorAliases(input: RunStartInput): void {
|
|
953
|
+
validateRunAliasPair(
|
|
954
|
+
"advisorHarnessId",
|
|
955
|
+
input.advisorHarnessId,
|
|
956
|
+
"synthesizerHarnessId",
|
|
957
|
+
input.synthesizerHarnessId,
|
|
958
|
+
);
|
|
959
|
+
validateRunAliasPair(
|
|
960
|
+
"advisorModel",
|
|
961
|
+
input.advisorModel,
|
|
962
|
+
"synthesizerModel",
|
|
963
|
+
input.synthesizerModel,
|
|
916
964
|
);
|
|
917
965
|
}
|
|
918
966
|
|
|
967
|
+
function validateRunAliasPair(
|
|
968
|
+
preferredName: string,
|
|
969
|
+
preferredValue: string | undefined,
|
|
970
|
+
legacyName: string,
|
|
971
|
+
legacyValue: string | undefined,
|
|
972
|
+
): void {
|
|
973
|
+
if (
|
|
974
|
+
preferredValue !== undefined &&
|
|
975
|
+
legacyValue !== undefined &&
|
|
976
|
+
preferredValue !== legacyValue
|
|
977
|
+
) {
|
|
978
|
+
throw new Error(
|
|
979
|
+
`Conflicting Run fields: ${preferredName} and deprecated ${legacyName}.`,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
919
984
|
function isTerminalRun(run: InternalRun): boolean {
|
|
920
985
|
return (
|
|
921
986
|
run.status === "succeeded" ||
|