even-pf 0.5.0 → 0.5.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/package.json +6 -6
- package/src/command-handler.ts +29 -1
- package/src/engine/engine.ts +39 -2
- package/src/hosts/cli-host.ts +12 -2
- package/src/util/config.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "even-pf",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "AI-assisted responsible grading tool for programming assignments",
|
|
5
5
|
"module": "src/hosts/cli-host.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"zod-defaults": "^0.2.3"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"even-pf-linux-x64": "0.5.
|
|
36
|
-
"even-pf-linux-arm64": "0.5.
|
|
37
|
-
"even-pf-windows-x64": "0.5.
|
|
38
|
-
"even-pf-darwin-x64": "0.5.
|
|
39
|
-
"even-pf-darwin-arm64": "0.5.
|
|
35
|
+
"even-pf-linux-x64": "0.5.2",
|
|
36
|
+
"even-pf-linux-arm64": "0.5.2",
|
|
37
|
+
"even-pf-windows-x64": "0.5.2",
|
|
38
|
+
"even-pf-darwin-x64": "0.5.2",
|
|
39
|
+
"even-pf-darwin-arm64": "0.5.2"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"bin/even-pf.js",
|
package/src/command-handler.ts
CHANGED
|
@@ -8,19 +8,23 @@ export type CommandResult = {
|
|
|
8
8
|
/** All recognized command names — exported for tab-completion. */
|
|
9
9
|
export const COMMAND_NAMES: readonly string[] = [
|
|
10
10
|
"run",
|
|
11
|
+
"rerun",
|
|
11
12
|
"clear",
|
|
12
13
|
"status",
|
|
13
14
|
"list",
|
|
15
|
+
"reload",
|
|
14
16
|
"help",
|
|
15
17
|
"exit",
|
|
16
18
|
"quit",
|
|
17
19
|
] as const;
|
|
18
20
|
|
|
19
21
|
const HELP_TEXT = `Available commands:
|
|
20
|
-
run [slug...]
|
|
22
|
+
run [slug...] Run workflows (all if no slug given)
|
|
23
|
+
rerun [slug...] Clear outputs then re-run workflows (all if no slug given)
|
|
21
24
|
clear [slug...] Clear output files (all if no slug given)
|
|
22
25
|
status Show in-flight workflows and output file count
|
|
23
26
|
list Show all configured workflow slugs
|
|
27
|
+
reload Reload config from the same source as startup
|
|
24
28
|
help Show this help message
|
|
25
29
|
exit / quit Shut down the program`;
|
|
26
30
|
|
|
@@ -42,12 +46,16 @@ export async function parseAndExecute(engine: Engine, rawInput: string): Promise
|
|
|
42
46
|
switch (command) {
|
|
43
47
|
case "run":
|
|
44
48
|
return await handleRun(engine, args);
|
|
49
|
+
case "rerun":
|
|
50
|
+
return await handleRerun(engine, args);
|
|
45
51
|
case "clear":
|
|
46
52
|
return handleClear(engine, args);
|
|
47
53
|
case "status":
|
|
48
54
|
return handleStatus(engine);
|
|
49
55
|
case "list":
|
|
50
56
|
return handleList(engine);
|
|
57
|
+
case "reload":
|
|
58
|
+
return await handleReload(engine);
|
|
51
59
|
case "help":
|
|
52
60
|
return { kind: "output", message: HELP_TEXT };
|
|
53
61
|
case "exit":
|
|
@@ -99,6 +107,16 @@ function handleClear(engine: Engine, slugs: string[]): CommandResult {
|
|
|
99
107
|
return { kind: "output", message: `Cleared results: ${target}` };
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
async function handleRerun(engine: Engine, slugs: string[]): Promise<CommandResult> {
|
|
111
|
+
// Resolve output_filename prefixes from config for the matched workflows so that
|
|
112
|
+
// clearing uses the config-defined filename pattern rather than the raw slug args.
|
|
113
|
+
// This is necessary because output filenames may be abbreviated relative to the slug.
|
|
114
|
+
const onlySlugs = slugs.length > 0 ? slugs : undefined;
|
|
115
|
+
const filePatterns = engine.resolveOutputFilePatterns(onlySlugs);
|
|
116
|
+
engine.clearResults(filePatterns.length > 0 ? filePatterns : undefined);
|
|
117
|
+
return await handleRun(engine, slugs);
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
function handleStatus(engine: Engine): CommandResult {
|
|
103
121
|
const status = engine.getStatus();
|
|
104
122
|
const lines: string[] = [];
|
|
@@ -137,3 +155,13 @@ function handleList(engine: Engine): CommandResult {
|
|
|
137
155
|
}
|
|
138
156
|
return { kind: "output", message: lines.join("\n") };
|
|
139
157
|
}
|
|
158
|
+
|
|
159
|
+
async function handleReload(engine: Engine): Promise<CommandResult> {
|
|
160
|
+
try {
|
|
161
|
+
await engine.reloadConfig();
|
|
162
|
+
return { kind: "output", message: "Config reloaded successfully." };
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
165
|
+
return { kind: "error", message: `Failed to reload config: ${message}` };
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/engine/engine.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { OutputViewer } from "../util/output-viewer.ts";
|
|
|
4
4
|
import { executeAnalysisWorkflow } from "../workflow/analysis-workflow.ts";
|
|
5
5
|
import { executeTestingWorkflow } from "../workflow/testing-workflow.ts";
|
|
6
6
|
import type { WorkflowDependencies } from "../workflow/index.ts";
|
|
7
|
+
import { readConfig } from "../util/config.ts";
|
|
7
8
|
import type { Config } from "../util/config.ts";
|
|
8
9
|
|
|
9
10
|
export type WorkflowRunResult = {
|
|
@@ -26,8 +27,8 @@ export type EngineStatus = {
|
|
|
26
27
|
*/
|
|
27
28
|
export class Engine {
|
|
28
29
|
readonly outputViewer: OutputViewer;
|
|
29
|
-
private
|
|
30
|
-
private
|
|
30
|
+
private config: Config;
|
|
31
|
+
private deps: WorkflowDependencies;
|
|
31
32
|
private readonly inFlightSlugs: Set<string> = new Set();
|
|
32
33
|
|
|
33
34
|
constructor(config: Config) {
|
|
@@ -118,11 +119,47 @@ export class Engine {
|
|
|
118
119
|
return results;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Reload config from the same source as the initial run.
|
|
124
|
+
* Reinitializes LLM settings and workflow lists.
|
|
125
|
+
* Does not affect output_viewing settings or in-flight workflows.
|
|
126
|
+
*/
|
|
127
|
+
async reloadConfig(): Promise<void> {
|
|
128
|
+
const newConfig = await readConfig();
|
|
129
|
+
this.config = newConfig;
|
|
130
|
+
this.deps = {
|
|
131
|
+
seed: Math.floor(Date.now() / 1000),
|
|
132
|
+
openRouter: new OpenRouter({
|
|
133
|
+
apiKey: newConfig.vendors.openrouter.api_key,
|
|
134
|
+
}),
|
|
135
|
+
outputViewer: this.outputViewer,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
/** Clear output files. If slugs provided, only clear matching filenames. */
|
|
122
140
|
clearResults(slugFilter?: string[]): void {
|
|
123
141
|
this.outputViewer.clearFiles(slugFilter);
|
|
124
142
|
}
|
|
125
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Resolve the static filename prefix for each workflow matched by the slug filter.
|
|
146
|
+
* Uses the literal portion of `output_filename` before the first `[` placeholder,
|
|
147
|
+
* which is the stable, config-defined part of the filename regardless of runtime
|
|
148
|
+
* values ([seed], [model], [run], etc.).
|
|
149
|
+
* Pass the result to clearResults() to accurately target the right output files.
|
|
150
|
+
*/
|
|
151
|
+
resolveOutputFilePatterns(slugs?: string[]): string[] {
|
|
152
|
+
const onlySlugs = slugs && slugs.length > 0 ? slugs : undefined;
|
|
153
|
+
const allWorkflows = [
|
|
154
|
+
...this.applyFilters(this.config.analysis_workflows, onlySlugs),
|
|
155
|
+
...this.applyFilters(this.config.testing_workflows, onlySlugs),
|
|
156
|
+
];
|
|
157
|
+
return allWorkflows.map((w) => {
|
|
158
|
+
const bracketIdx = w.output_filename.indexOf("[");
|
|
159
|
+
return bracketIdx === -1 ? w.output_filename : w.output_filename.slice(0, bracketIdx);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
126
163
|
/** Return current engine state snapshot. */
|
|
127
164
|
getStatus(): EngineStatus {
|
|
128
165
|
return {
|
package/src/hosts/cli-host.ts
CHANGED
|
@@ -81,9 +81,9 @@ function completer(line: string): [string[], string] {
|
|
|
81
81
|
return [hits, trimmed];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// Completing workflow slugs for "run" and "clear"
|
|
84
|
+
// Completing workflow slugs for "run", "rerun", and "clear"
|
|
85
85
|
const command = parts[0]!.toLowerCase();
|
|
86
|
-
if (command === "run" || command === "clear") {
|
|
86
|
+
if (command === "run" || command === "rerun" || command === "clear") {
|
|
87
87
|
const partial = parts[parts.length - 1]!;
|
|
88
88
|
const workflows = engine.listWorkflows();
|
|
89
89
|
const allSlugs = [...workflows.analysis, ...workflows.testing];
|
|
@@ -94,6 +94,16 @@ function completer(line: string): [string[], string] {
|
|
|
94
94
|
return [[], line];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// --- Catch SDK-internal async errors that escape try/catch ---
|
|
98
|
+
// The OpenRouter SDK can throw network errors (e.g. ECONNRESET) from
|
|
99
|
+
// internally-spawned micro-tasks (e.g. response.text() inside matchFunc),
|
|
100
|
+
// after chat.send() has already been awaited. These surface as unhandled
|
|
101
|
+
// promise rejections and would crash Bun without this handler.
|
|
102
|
+
process.on("unhandledRejection", (reason: unknown) => {
|
|
103
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
104
|
+
console.error(chalk.red(`[epf] Unhandled async error (process kept alive): ${message}`));
|
|
105
|
+
});
|
|
106
|
+
|
|
97
107
|
// --- REPL loop ---
|
|
98
108
|
const rl = createInterface({
|
|
99
109
|
input: process.stdin,
|
package/src/util/config.ts
CHANGED