klyro 1.0.10 → 1.0.11

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.
@@ -79,6 +79,8 @@ export interface EvalResult {
79
79
  notes: string;
80
80
  skipped: boolean;
81
81
  };
82
+ /** Isolated workdir the scenario ran in (tmp unless --cwd). Debugging aid. */
83
+ workDir?: string;
82
84
  }
83
85
  export interface RunEvalOptions {
84
86
  inputPath: string;
@@ -90,10 +92,16 @@ export interface RunEvalOptions {
90
92
  model?: string;
91
93
  /** Live model id for grading `judge.rubric` (env endpoint + key required). */
92
94
  judgeModel?: string;
95
+ /**
96
+ * Shared workdir for JSONL scenarios. When omitted each scenario runs in
97
+ * a fresh tmp dir (deleted afterwards) so scripted tool calls can never
98
+ * touch the caller's directory. Pass explicitly to inspect artifacts.
99
+ */
100
+ cwd?: string;
93
101
  }
94
102
  export declare function runEval(opts: RunEvalOptions): Promise<number>;
95
103
  export declare function scriptedAdapterFromSpec(spec: Array<Array<unknown[]>> | undefined): ProviderAdapter;
96
104
  export declare function runScenario(sc: EvalScenario, judgeOpts?: {
97
105
  adapter: ProviderAdapter;
98
106
  model: string;
99
- }): Promise<EvalResult>;
107
+ }, workDir?: string): Promise<EvalResult>;
package/dist/cli/eval.js CHANGED
@@ -40,6 +40,9 @@
40
40
  * otherwise.
41
41
  */
42
42
  import * as fs from 'node:fs';
43
+ import * as fsp from 'node:fs/promises';
44
+ import * as os from 'node:os';
45
+ import * as path from 'node:path';
43
46
  import * as readline from 'node:readline/promises';
44
47
  import { stdin as input, stdout, stderr } from 'node:process';
45
48
  import { run } from '../agent/runtime.js';
@@ -153,7 +156,7 @@ export async function runEval(opts) {
153
156
  const results = [];
154
157
  for (const sc of scenarios) {
155
158
  const start = Date.now();
156
- const r = await runScenario(sc, judgeAdapter && opts.judgeModel ? { adapter: judgeAdapter, model: opts.judgeModel } : undefined);
159
+ const r = await runScenario(sc, judgeAdapter && opts.judgeModel ? { adapter: judgeAdapter, model: opts.judgeModel } : undefined, opts.cwd);
157
160
  r.durationMs = Date.now() - start;
158
161
  results.push(r);
159
162
  if (opts.output === 'json') {
@@ -242,35 +245,53 @@ function tupleToEvent(tuple) {
242
245
  throw new Error(`scriptedAdapterFromSpec: unknown event kind: ${kind}`);
243
246
  }
244
247
  }
245
- export async function runScenario(sc, judgeOpts) {
248
+ export async function runScenario(sc, judgeOpts, workDir) {
246
249
  const failures = [];
247
250
  const model = sc.model ?? 'mock';
248
251
  const adapter = scriptedAdapterFromSpec(sc.scripted_events);
249
252
  const registry = builtinRegistry();
250
253
  const policy = new PolicyEngine(builtinRules(), DEFAULT_POLICY_CONFIG);
251
- const result = await run({
252
- task: sc.task,
253
- cwd: process.cwd(),
254
- model,
255
- maxSteps: sc.maxSteps,
256
- maxTokens: sc.maxTokens,
257
- nonInteractive: true,
258
- ...(sc.verify
259
- ? {
260
- verify: {
261
- enabled: true,
262
- ...(sc.verify.command !== undefined ? { command: sc.verify.command } : {}),
263
- ...(sc.verify.mode !== undefined ? { mode: sc.verify.mode } : {}),
264
- },
254
+ // Isolation (fix: scripted tool calls must never run in the caller's
255
+ // directory — a JSONL scenario writing a.txt/b.txt used to pollute it).
256
+ // Explicit workDir is shared as-is (inspect artifacts); otherwise each
257
+ // scenario gets a fresh tmp dir that is removed afterwards.
258
+ const owned = !workDir;
259
+ const cwd = workDir ?? await fsp.mkdtemp(path.join(os.tmpdir(), 'klyro-eval-jsonl-'));
260
+ await fsp.mkdir(cwd, { recursive: true });
261
+ let result;
262
+ try {
263
+ result = await run({
264
+ task: sc.task,
265
+ cwd,
266
+ model,
267
+ maxSteps: sc.maxSteps,
268
+ maxTokens: sc.maxTokens,
269
+ nonInteractive: true,
270
+ ...(sc.verify
271
+ ? {
272
+ verify: {
273
+ enabled: true,
274
+ ...(sc.verify.command !== undefined ? { command: sc.verify.command } : {}),
275
+ ...(sc.verify.mode !== undefined ? { mode: sc.verify.mode } : {}),
276
+ },
277
+ }
278
+ : {}),
279
+ }, {
280
+ adapter,
281
+ registry,
282
+ policy,
283
+ approval: new DenyAllApprovalPrompt(),
284
+ systemPrompt: ({ cwd }) => `You are Klyro. cwd=${cwd}.`,
285
+ });
286
+ }
287
+ finally {
288
+ if (owned) {
289
+ try {
290
+ await fsp.rm(cwd, { recursive: true, force: true });
265
291
  }
266
- : {}),
267
- }, {
268
- adapter,
269
- registry,
270
- policy,
271
- approval: new DenyAllApprovalPrompt(),
272
- systemPrompt: ({ cwd }) => `You are Klyro. cwd=${cwd}.`,
273
- });
292
+ catch { /* ignore */ }
293
+ }
294
+ }
274
295
  const exp = sc.expect ?? {};
275
296
  if (exp.status !== undefined && result.status !== exp.status) {
276
297
  failures.push(`status: expected ${exp.status}, got ${result.status}`);
@@ -311,6 +332,7 @@ export async function runScenario(sc, judgeOpts) {
311
332
  toolCalls: result.toolCalls,
312
333
  text: result.finalText,
313
334
  durationMs: 0,
335
+ workDir: cwd,
314
336
  ...(judge ? { judge } : {}),
315
337
  };
316
338
  }
package/dist/index.js CHANGED
@@ -390,6 +390,7 @@ async function main() {
390
390
  .option('--parallel <n>', 'Parallelism (default 1)', (v) => parsePositiveInt('--parallel', v))
391
391
  .option('--model <id>', 'Model for eval')
392
392
  .option('--judge-model <id>', 'Live model id for grading judge.rubric (needs endpoint + key)')
393
+ .option('--cwd <path>', 'Shared scenario workdir (default: isolated tmp per scenario)')
393
394
  .action(async (input, opts) => {
394
395
  const output = (opts.output ?? 'human');
395
396
  if (opts.suite) {
@@ -400,7 +401,7 @@ async function main() {
400
401
  process.stderr.write('klyro eval: missing input (provide <input> or --suite)\n');
401
402
  process.exit(2);
402
403
  }
403
- const code = await runEval({ inputPath: input, output, suite: opts.suite, filter: opts.filter, runs: opts.runs, parallel: opts.parallel, model: opts.model, judgeModel: opts.judgeModel });
404
+ const code = await runEval({ inputPath: input, output, suite: opts.suite, filter: opts.filter, runs: opts.runs, parallel: opts.parallel, model: opts.model, judgeModel: opts.judgeModel, cwd: opts.cwd });
404
405
  process.exit(code);
405
406
  });
406
407
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",