klyro 1.0.0 → 1.0.1

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.
Files changed (96) hide show
  1. package/dist/agent/anthropic-adapter.d.ts +36 -0
  2. package/dist/agent/anthropic-adapter.js +68 -16
  3. package/dist/agent/capabilities.d.ts +23 -0
  4. package/dist/agent/capabilities.js +46 -5
  5. package/dist/agent/child-worker.d.ts +104 -0
  6. package/dist/agent/child-worker.js +250 -0
  7. package/dist/agent/orchestrator.d.ts +106 -5
  8. package/dist/agent/orchestrator.js +378 -58
  9. package/dist/agent/provider-adapter.d.ts +8 -0
  10. package/dist/agent/provider-adapter.js +12 -3
  11. package/dist/agent/retry.d.ts +1 -1
  12. package/dist/agent/retry.js +2 -2
  13. package/dist/agent/runtime.d.ts +50 -8
  14. package/dist/agent/runtime.js +196 -33
  15. package/dist/agent/worktree-manager.d.ts +74 -0
  16. package/dist/agent/worktree-manager.js +189 -0
  17. package/dist/checkpoints/store.js +30 -5
  18. package/dist/cli/auth.js +16 -1
  19. package/dist/cli/config.d.ts +9 -3
  20. package/dist/cli/config.js +64 -3
  21. package/dist/cli/eval.d.ts +6 -1
  22. package/dist/cli/eval.js +9 -0
  23. package/dist/cli/repl.js +155 -28
  24. package/dist/cli/run.d.ts +7 -11
  25. package/dist/cli/run.js +68 -17
  26. package/dist/cli/update.d.ts +5 -0
  27. package/dist/cli/update.js +62 -10
  28. package/dist/context/import-graph.d.ts +2 -0
  29. package/dist/context/import-graph.js +31 -3
  30. package/dist/context/klyro-md.js +4 -1
  31. package/dist/context/memory.d.ts +8 -0
  32. package/dist/context/memory.js +50 -2
  33. package/dist/context/project-map.d.ts +6 -0
  34. package/dist/context/project-map.js +50 -2
  35. package/dist/context/repo-map.d.ts +2 -0
  36. package/dist/context/repo-map.js +31 -1
  37. package/dist/events/catalog.d.ts +28 -0
  38. package/dist/index.js +89 -4
  39. package/dist/mcp/client.d.ts +6 -4
  40. package/dist/mcp/client.js +83 -14
  41. package/dist/mcp/config.d.ts +10 -0
  42. package/dist/mcp/config.js +18 -1
  43. package/dist/mcp/registry.d.ts +23 -1
  44. package/dist/mcp/registry.js +78 -6
  45. package/dist/mcp/schema.d.ts +11 -4
  46. package/dist/mcp/schema.js +27 -16
  47. package/dist/mcp/trust.d.ts +20 -0
  48. package/dist/mcp/trust.js +74 -0
  49. package/dist/persistence/audit.d.ts +28 -0
  50. package/dist/persistence/audit.js +101 -1
  51. package/dist/persistence/store.d.ts +26 -2
  52. package/dist/persistence/store.js +140 -13
  53. package/dist/policy/approval.d.ts +14 -0
  54. package/dist/policy/approval.js +44 -2
  55. package/dist/policy/engine.d.ts +1 -0
  56. package/dist/policy/engine.js +88 -8
  57. package/dist/policy/secret-redactor.js +4 -0
  58. package/dist/providers/model-info.d.ts +17 -0
  59. package/dist/providers/model-info.js +35 -2
  60. package/dist/repl.d.ts +6 -0
  61. package/dist/repl.js +12 -7
  62. package/dist/tools/agent/spawn-agent.js +5 -5
  63. package/dist/tools/agent/task-apply.d.ts +4 -0
  64. package/dist/tools/agent/task-apply.js +44 -0
  65. package/dist/tools/agent/task-stop.d.ts +6 -0
  66. package/dist/tools/agent/task-stop.js +39 -0
  67. package/dist/tools/agent/task-wait.d.ts +17 -0
  68. package/dist/tools/agent/task-wait.js +79 -0
  69. package/dist/tools/fs/apply-patch.js +71 -0
  70. package/dist/tools/fs/edit-file.js +65 -0
  71. package/dist/tools/fs/multi-edit.d.ts +4 -0
  72. package/dist/tools/fs/multi-edit.js +66 -0
  73. package/dist/tools/fs/write-file.js +67 -0
  74. package/dist/tools/registry.js +6 -0
  75. package/dist/tools/shell/background.js +6 -3
  76. package/dist/tools/shell/sandbox.d.ts +51 -0
  77. package/dist/tools/shell/sandbox.js +143 -0
  78. package/dist/tools/shell/shell-exec.d.ts +1 -0
  79. package/dist/tools/shell/shell-exec.js +83 -11
  80. package/dist/tools/shell/worker-entry.d.ts +12 -0
  81. package/dist/tools/shell/worker-entry.js +43 -0
  82. package/dist/tools/types.d.ts +6 -0
  83. package/dist/tools/verify/run-verify.js +3 -1
  84. package/dist/trace/writer.d.ts +13 -0
  85. package/dist/trace/writer.js +55 -4
  86. package/dist/tui/app.js +1 -1
  87. package/dist/tui/approval.js +20 -21
  88. package/dist/util.d.ts +1 -0
  89. package/dist/util.js +1 -0
  90. package/dist/verification/baseline.js +17 -3
  91. package/dist/verification/classify.js +23 -12
  92. package/dist/verification/engine.js +3 -1
  93. package/dist/verification/registry.d.ts +2 -0
  94. package/dist/verification/registry.js +33 -0
  95. package/dist/verification/scoped.js +28 -6
  96. package/package.json +1 -1
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Git worktree isolation for parallel write-capable child agents.
3
+ *
4
+ * Each write-capable child gets its own worktree on a branch
5
+ * `klyro/<taskId>` so concurrent children never clobber each other's files.
6
+ * `task_apply` merges the branch back into the parent tree; failures,
7
+ * cancellations, and timeouts remove the worktree best-effort.
8
+ *
9
+ * Semantics worth knowing:
10
+ * - Dirty parent: the merge runs in the parent checkout, so uncommitted
11
+ * parent edits can conflict. On conflict the merge is ABORTED (no
12
+ * commit), the branch is kept, and the caller gets MERGE_CONFLICT with
13
+ * the file list — resolve and re-apply.
14
+ * - Stale branch: a leftover `klyro/<taskId>` branch (e.g. from a crashed
15
+ * session) makes `createWorktree` fail. Delete the stale branch
16
+ * (`deleteBranch`) or sweep orphaned worktrees (`pruneStaleWorktrees`)
17
+ * before retrying.
18
+ * - No-apply: tasks that never reach `succeeded` are NEVER merged — their
19
+ * worktrees are removed best-effort and `task_apply` reports NOT_READY.
20
+ *
21
+ * All git invocations go through `execFile` argv (no shell) with a ~30s
22
+ * timeout, so this is safe on Windows.
23
+ */
24
+ import { execFile } from 'node:child_process';
25
+ import { promisify } from 'node:util';
26
+ import * as path from 'node:path';
27
+ import { filteredEnv } from '../tools/shell/shell-exec.js';
28
+ const execFileAsync = promisify(execFile);
29
+ /** Per-command timeout for every git invocation. */
30
+ export const GIT_TIMEOUT_MS = 30_000;
31
+ async function git(repoCwd, args) {
32
+ // Filtered env (same call shape as shell-exec) so secrets never reach
33
+ // child processes. Then strip repo-pinning vars so discovery is always
34
+ // relative to `repoCwd` (a sandbox may export GIT_DIR for its own
35
+ // harness — that must not make every directory look like a repo).
36
+ const env = { ...filteredEnv() };
37
+ delete env.GIT_DIR;
38
+ delete env.GIT_WORK_TREE;
39
+ delete env.GIT_NAMESPACE;
40
+ const r = await execFileAsync('git', [...args], {
41
+ cwd: repoCwd,
42
+ timeout: GIT_TIMEOUT_MS,
43
+ windowsHide: true,
44
+ maxBuffer: 4 * 1024 * 1024,
45
+ env,
46
+ });
47
+ return { stdout: r.stdout, stderr: r.stderr };
48
+ }
49
+ /** True when `cwd` is inside a git working tree. Never throws — false on any failure. */
50
+ export async function ensureGitRepo(cwd) {
51
+ try {
52
+ const r = await git(cwd, ['rev-parse', '--is-inside-work-tree']);
53
+ return r.stdout.trim() === 'true';
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /** Resolve the repo top-level for any path inside it. Throws when not in a repo. */
60
+ export async function repoTopLevel(cwd) {
61
+ const r = await git(cwd, ['rev-parse', '--show-toplevel']);
62
+ return r.stdout.trim();
63
+ }
64
+ function branchForTask(taskId) {
65
+ return `klyro/${taskId}`;
66
+ }
67
+ /**
68
+ * Create an isolated worktree for a task on branch `klyro/<taskId>`,
69
+ * rooted at `<repo>/.klyro/worktrees/<taskId>` from HEAD.
70
+ */
71
+ export async function createWorktree(opts) {
72
+ const branch = branchForTask(opts.taskId);
73
+ const top = await repoTopLevel(opts.repoCwd);
74
+ const worktreePath = path.join(top, '.klyro', 'worktrees', opts.taskId);
75
+ await git(opts.repoCwd, ['worktree', 'add', '-b', branch, worktreePath, 'HEAD']);
76
+ return { worktreePath, branch };
77
+ }
78
+ /**
79
+ * Merge a task branch into the current checkout (`repoCwd`) with
80
+ * `git merge --no-ff --no-edit`. On conflict, parses `git status
81
+ * --porcelain` for unmerged paths (UU/AA/DD), aborts the merge WITHOUT
82
+ * committing, and returns `{ merged: false, conflictFiles }`.
83
+ */
84
+ export async function mergeWorktree(opts) {
85
+ try {
86
+ await git(opts.repoCwd, ['merge', '--no-ff', '--no-edit', opts.branch]);
87
+ return { merged: true, conflictFiles: [] };
88
+ }
89
+ catch {
90
+ const conflictFiles = await unmergedFiles(opts.repoCwd);
91
+ try {
92
+ await git(opts.repoCwd, ['merge', '--abort']);
93
+ }
94
+ catch {
95
+ /* best-effort: leave the repo as-is if abort itself fails */
96
+ }
97
+ return { merged: false, conflictFiles };
98
+ }
99
+ }
100
+ async function unmergedFiles(repoCwd) {
101
+ try {
102
+ const r = await git(repoCwd, ['status', '--porcelain']);
103
+ const out = [];
104
+ for (const line of r.stdout.split('\n')) {
105
+ if (line.length < 4)
106
+ continue;
107
+ const x = line[0];
108
+ const y = line[1];
109
+ const unmerged = (x === 'U' || y === 'U' || x === 'A' || y === 'A') && (x === 'U' || y === 'U' || (x === 'A' && y === 'A'));
110
+ // Unmerged statuses: UU, AA, DD, AU, UA, UD, DU.
111
+ const code = `${x}${y}`;
112
+ if (['UU', 'AA', 'DD', 'AU', 'UA', 'UD', 'DU'].includes(code) || unmerged) {
113
+ out.push(line.slice(3).trim());
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
122
+ /** Remove a worktree and prune its metadata. Throws with context on failure. */
123
+ export async function removeWorktree(opts) {
124
+ const args = ['worktree', 'remove', ...(opts.force ? ['--force'] : []), opts.worktreePath];
125
+ try {
126
+ await git(opts.repoCwd, args);
127
+ }
128
+ catch (err) {
129
+ throw new Error(`git worktree remove failed for ${opts.worktreePath}: ${err instanceof Error ? err.message : String(err)}`);
130
+ }
131
+ finally {
132
+ try {
133
+ await git(opts.repoCwd, ['worktree', 'prune']);
134
+ }
135
+ catch {
136
+ /* best-effort */
137
+ }
138
+ }
139
+ }
140
+ /** Delete a task branch (best-effort cleanup after a successful merge). Never throws. */
141
+ export async function deleteBranch(opts) {
142
+ try {
143
+ await git(opts.repoCwd, ['branch', '-D', opts.branch]);
144
+ }
145
+ catch {
146
+ /* best-effort */
147
+ }
148
+ }
149
+ /**
150
+ * Remove worktrees under `.klyro/worktrees` whose task id is NOT in
151
+ * `activeTaskIds` (orphans from crashed/timed-out sessions). Lists
152
+ * `git worktree list --porcelain`, force-removes each stale entry, and
153
+ * returns the removed paths. Never throws — per-worktree failures are
154
+ * skipped and a failing `git` itself yields `[]`.
155
+ */
156
+ export async function pruneStaleWorktrees(repoCwd, activeTaskIds) {
157
+ const removed = [];
158
+ try {
159
+ const r = await git(repoCwd, ['worktree', 'list', '--porcelain']);
160
+ const active = new Set(activeTaskIds);
161
+ for (const line of r.stdout.split('\n')) {
162
+ const raw = /^worktree (.+)$/.exec(line.trim())?.[1];
163
+ if (!raw)
164
+ continue;
165
+ // git prints forward slashes even on win32 — normalize so returned
166
+ // paths textually match the path.join-built worktreePath.
167
+ const wp = path.normalize(raw);
168
+ // Only manage our own namespace: <repo>/.klyro/worktrees/<taskId>.
169
+ const parts = wp.split(/[\\/]/);
170
+ const idx = parts.lastIndexOf('worktrees');
171
+ if (idx < 1 || parts[idx - 1] !== '.klyro' || idx + 1 >= parts.length)
172
+ continue;
173
+ const taskId = parts[idx + 1];
174
+ if (!taskId || active.has(taskId))
175
+ continue;
176
+ try {
177
+ await removeWorktree({ repoCwd, worktreePath: wp, force: true });
178
+ removed.push(wp);
179
+ }
180
+ catch {
181
+ /* skip — never throws */
182
+ }
183
+ }
184
+ }
185
+ catch {
186
+ /* git itself failed (not a repo, binary missing) — return what we have */
187
+ }
188
+ return removed;
189
+ }
@@ -7,6 +7,19 @@ import * as crypto from 'node:crypto';
7
7
  function ckptDir(cwd) {
8
8
  return path.join(cwd, '.klyro', 'checkpoints');
9
9
  }
10
+ /** Best-effort fsync of a just-written file (crash safety). */
11
+ async function fsyncFile(p) {
12
+ try {
13
+ const fh = await fs.open(p, 'r+');
14
+ try {
15
+ await fh.sync();
16
+ }
17
+ finally {
18
+ await fh.close();
19
+ }
20
+ }
21
+ catch { /* ignore on Windows */ }
22
+ }
10
23
  /**
11
24
  * Resolve a checkpoint file list entry inside cwd. Returns null for anything
12
25
  * escaping the project (no arbitrary read/write outside cwd, via either the
@@ -39,6 +52,7 @@ export async function snapshot(cwd, files) {
39
52
  continue;
40
53
  await fs.mkdir(path.dirname(out), { recursive: true });
41
54
  await fs.writeFile(out, data);
55
+ await fsyncFile(out);
42
56
  kept.push(rel);
43
57
  }
44
58
  catch (e) {
@@ -47,8 +61,11 @@ export async function snapshot(cwd, files) {
47
61
  missing.push(f);
48
62
  }
49
63
  }
50
- // Save meta
51
- await fs.writeFile(path.join(dest, '.meta.json'), JSON.stringify({ id, files: kept, missing, ts: Date.now() }, null, 2));
64
+ // Save meta (fsync before the checkpoint is visible — mirrors the
65
+ // SessionStore.writeIndex atomic pattern).
66
+ const metaPath = path.join(dest, '.meta.json');
67
+ await fs.writeFile(metaPath, JSON.stringify({ id, files: kept, missing, ts: Date.now() }, null, 2));
68
+ await fsyncFile(metaPath);
52
69
  // Best-effort last.diff for the repair guard (guardRepair reads it).
53
70
  try {
54
71
  const { spawn } = await import('node:child_process');
@@ -86,8 +103,15 @@ export async function snapshot(cwd, files) {
86
103
  resolve('');
87
104
  });
88
105
  });
89
- if (diffText)
106
+ if (diffText) {
90
107
  await fs.writeFile(path.join(dir, 'last.diff'), diffText, 'utf-8');
108
+ // Per-checkpoint diff file (best-effort); the repair guard keeps
109
+ // reading last.diff, so its behavior is unchanged.
110
+ try {
111
+ await fs.writeFile(path.join(dir, `${id}.diff`), diffText, 'utf-8');
112
+ }
113
+ catch { /* best-effort only */ }
114
+ }
91
115
  }
92
116
  catch { /* best-effort only */ }
93
117
  return id;
@@ -96,8 +120,9 @@ export async function listCheckpoints(cwd) {
96
120
  const dir = ckptDir(cwd);
97
121
  try {
98
122
  const entries = await fs.readdir(dir);
99
- // last.diff is a guard artifact, not a checkpoint (must never be an undo target).
100
- return entries.filter((e) => !e.startsWith('.') && e !== 'last.diff').sort();
123
+ // last.diff is a guard artifact and <id>.diff files are per-checkpoint
124
+ // diffs — neither is a checkpoint (must never be an undo target).
125
+ return entries.filter((e) => !e.startsWith('.') && e !== 'last.diff' && !e.endsWith('.diff')).sort();
101
126
  }
102
127
  catch {
103
128
  return [];
package/dist/cli/auth.js CHANGED
@@ -111,7 +111,11 @@ export async function runLogout(provider) {
111
111
  const creds = JSON.parse(raw);
112
112
  if (provider) {
113
113
  delete creds[provider];
114
- await fs.writeFile(credPath(), JSON.stringify(creds, null, 2), 'utf-8');
114
+ await fs.writeFile(credPath(), JSON.stringify(creds, null, 2), { mode: 0o600 });
115
+ try {
116
+ await fs.chmod(credPath(), 0o600);
117
+ }
118
+ catch { /* ignore on Windows */ }
115
119
  process.stdout.write(`Removed ${provider} key\n`);
116
120
  }
117
121
  else {
@@ -128,6 +132,17 @@ export async function runLogout(provider) {
128
132
  }
129
133
  export function getStoredKey(provider) {
130
134
  try {
135
+ // Warn (don't refuse) when the credentials file is group/other-readable.
136
+ // Refusing would lock out existing users with old umasks; warn instead.
137
+ if (process.platform !== 'win32') {
138
+ try {
139
+ const st = fsSync.statSync(credPath());
140
+ if ((st.mode & 0o077) !== 0) {
141
+ process.stderr.write(`warning: credentials file ${credPath()} is group/other-readable (mode ${(st.mode & 0o777).toString(8)}) — run chmod 600 on it\n`);
142
+ }
143
+ }
144
+ catch { /* missing file → no warning */ }
145
+ }
131
146
  const raw = fsSync.readFileSync(credPath(), 'utf-8');
132
147
  const creds = JSON.parse(raw);
133
148
  const v = creds[provider];
@@ -46,12 +46,18 @@ export interface PermissionRules {
46
46
  * without re-prompting.
47
47
  */
48
48
  export declare function loadPermissionRules(cwd?: string): Promise<PermissionRules>;
49
+ export declare function isValidAllowRule(rule: string): boolean;
49
50
  /**
50
- * Persist an "always allow" pattern to the home settings file
51
- * (~/.klyro/settings.json, honors KLYRO_CONFIG). Returns whether it was
51
+ * Persist an "always allow" pattern. Validates the rule against the strict
52
+ * grammar (throws `invalid rule` otherwise). When `cwd` is a project dir
53
+ * (exists + contains .git or .klyro), writes to <cwd>/.klyro/settings.json;
54
+ * otherwise writes to the home settings file. Returns whether it was
52
55
  * added (false when already present) and the file written.
56
+ *
57
+ * NOTE for the repl integrator (sibling-owned repl.ts): pass the session
58
+ * cwd as the second arg to persist project-scoped allows.
53
59
  */
54
- export declare function persistAllowRule(rule: string): Promise<{
60
+ export declare function persistAllowRule(rule: string, cwd?: string): Promise<{
55
61
  added: boolean;
56
62
  path: string;
57
63
  }>;
@@ -338,11 +338,72 @@ export async function loadPermissionRules(cwd = process.cwd()) {
338
338
  };
339
339
  }
340
340
  /**
341
- * Persist an "always allow" pattern to the home settings file
342
- * (~/.klyro/settings.json, honors KLYRO_CONFIG). Returns whether it was
341
+ * Strict allow-rule grammar: `tool` or `tool(glob)` where tool is
342
+ * lowercase alnum+underscore and glob contains no parens.
343
+ */
344
+ const ALLOW_RULE_RE = /^[a-z0-9_]+\([^()]*\)$|^[a-z0-9_]+$/;
345
+ export function isValidAllowRule(rule) {
346
+ return ALLOW_RULE_RE.test(rule.trim());
347
+ }
348
+ function isProjectDir(cwd) {
349
+ try {
350
+ const st = fsSync.statSync(cwd);
351
+ if (!st.isDirectory())
352
+ return false;
353
+ }
354
+ catch {
355
+ return false;
356
+ }
357
+ return fsSync.existsSync(path.join(cwd, '.git')) || fsSync.existsSync(path.join(cwd, '.klyro'));
358
+ }
359
+ /**
360
+ * Persist an "always allow" pattern. Validates the rule against the strict
361
+ * grammar (throws `invalid rule` otherwise). When `cwd` is a project dir
362
+ * (exists + contains .git or .klyro), writes to <cwd>/.klyro/settings.json;
363
+ * otherwise writes to the home settings file. Returns whether it was
343
364
  * added (false when already present) and the file written.
365
+ *
366
+ * NOTE for the repl integrator (sibling-owned repl.ts): pass the session
367
+ * cwd as the second arg to persist project-scoped allows.
344
368
  */
345
- export async function persistAllowRule(rule) {
369
+ export async function persistAllowRule(rule, cwd) {
370
+ if (!isValidAllowRule(rule)) {
371
+ throw new Error(`invalid rule: ${rule}`);
372
+ }
373
+ // Project-scoped persist when cwd is a project dir.
374
+ if (cwd && isProjectDir(cwd)) {
375
+ const projPath = path.join(cwd, '.klyro', 'settings.json');
376
+ let cfg = {};
377
+ try {
378
+ const raw = await fs.readFile(projPath, 'utf-8');
379
+ cfg = validateConfig(parseJsonc(raw, projPath), projPath);
380
+ }
381
+ catch (err) {
382
+ const e = err;
383
+ if (e.code !== 'ENOENT')
384
+ throw err;
385
+ }
386
+ const allow = asStringArray(cfg.allow);
387
+ if (allow.includes(rule))
388
+ return { added: false, path: projPath };
389
+ cfg.allow = [...allow, rule];
390
+ await fs.mkdir(path.dirname(projPath), { recursive: true });
391
+ const tmp = `${projPath}.tmp-${process.pid}-${Date.now()}`;
392
+ const data = JSON.stringify(validateConfig(cfg, projPath), null, 2) + '\n';
393
+ await fs.writeFile(tmp, data, { mode: 0o600 });
394
+ try {
395
+ await fs.rename(tmp, projPath);
396
+ }
397
+ catch (err) {
398
+ await fs.unlink(tmp).catch(() => undefined);
399
+ throw err;
400
+ }
401
+ try {
402
+ await fs.chmod(projPath, 0o600);
403
+ }
404
+ catch { /* ignore on Windows */ }
405
+ return { added: true, path: projPath };
406
+ }
346
407
  const cfg = await loadConfig();
347
408
  const allow = asStringArray(cfg.allow);
348
409
  if (allow.includes(rule))
@@ -39,7 +39,7 @@
39
39
  * is set, emits one JSON object per line. Exit code 0 if all pass, 1
40
40
  * otherwise.
41
41
  */
42
- import { type RunResult } from '../agent/runtime.js';
42
+ import { type RunResult, type VerifyMode } from '../agent/runtime.js';
43
43
  import type { ProviderAdapter } from '../agent/provider-adapter.js';
44
44
  export interface EvalScenario {
45
45
  name: string;
@@ -47,6 +47,11 @@ export interface EvalScenario {
47
47
  model?: string;
48
48
  maxSteps?: number;
49
49
  maxTokens?: number;
50
+ /** Verification passthrough (default unchanged: strict pipeline when omitted). */
51
+ verify?: {
52
+ mode?: VerifyMode;
53
+ command?: string;
54
+ };
50
55
  /** Adapter events to script, as [eventKind, ...args] tuples. */
51
56
  scripted_events?: Array<Array<unknown[]>>;
52
57
  expect?: {
package/dist/cli/eval.js CHANGED
@@ -236,6 +236,15 @@ export async function runScenario(sc) {
236
236
  maxSteps: sc.maxSteps,
237
237
  maxTokens: sc.maxTokens,
238
238
  nonInteractive: true,
239
+ ...(sc.verify
240
+ ? {
241
+ verify: {
242
+ enabled: true,
243
+ ...(sc.verify.command !== undefined ? { command: sc.verify.command } : {}),
244
+ ...(sc.verify.mode !== undefined ? { mode: sc.verify.mode } : {}),
245
+ },
246
+ }
247
+ : {}),
239
248
  }, {
240
249
  adapter,
241
250
  registry,