@zenera/engine 1.1.53 → 1.1.55

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/dist/engine.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { AgentRunner, Workspace, type AgentEvent, type AgentProject, type AgentState, type Input, type MemoryIndex, type RunIdentity, type RunResult } from '@zenera/neo';
1
+ import { AgentRunner, Workspace, type AgentEvent, type AgentProject, type AgentState, type Input, type ResumeAnswer, type MemoryIndex, type RunIdentity, type RunResult } from '@zenera/neo';
2
2
  import { type ModelIssue } from './audit.ts';
3
3
  import { type Logger } from './errors.ts';
4
- import { type Project } from './projects.ts';
5
4
  import { type ZenPlugin } from './plugins.ts';
5
+ import { type Project } from './projects.ts';
6
6
  import { type Preflight, type SandboxSetup } from './sandbox.ts';
7
7
  import { type Held, type RunPaths, type SessionPaths } from './session.ts';
8
8
  /** Who a run acts for — re-exported so a host needs only this package. */
@@ -27,9 +27,9 @@ export interface EngineOptions {
27
27
  /** recall only: nothing is written and several runs may share one graph */
28
28
  memoryReadOnly?: boolean;
29
29
  /**
30
- * An already-open graph to remember into instead of opening `memoryDir`. A
31
- * memory directory is locked by whoever opens it, so a host running several
32
- * sessions over one graph opens it once and hands the same index to each.
30
+ * An already-open graph to remember into instead of opening `memoryDir`.
31
+ * Sessions may each open the same directory; handing them one index saves
32
+ * every session loading the graph again.
33
33
  * Name its directory in `memoryDir` too: that is what is mounted at `/memory`.
34
34
  */
35
35
  memory?: MemoryIndex;
@@ -148,4 +148,11 @@ export declare function envelope(engine: Engine, outcome: RunOutcome): Record<st
148
148
  * command.
149
149
  */
150
150
  export declare function runEngine(engine: Engine, input: Input, onEvent?: (event: AgentEvent) => void, signal?: AbortSignal): Promise<RunOutcome>;
151
+ /**
152
+ * Continues a session whose last run stopped `suspended`: `answers` answer its
153
+ * suspended calls (re-run, a result, or cancelled - see `ResumeAnswer`). The
154
+ * run is recorded like any other turn; its input reads as the answers given.
155
+ * Calls left unanswered keep it suspended.
156
+ */
157
+ export declare function resumeEngine(engine: Engine, answers: Record<string, ResumeAnswer>, onEvent?: (event: AgentEvent) => void, signal?: AbortSignal): Promise<RunOutcome>;
151
158
  //# sourceMappingURL=engine.d.ts.map
package/dist/engine.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AgentRunner, FilePayloadStore, SANDBOX_MOUNT, SKILLS_MOUNT, Workspace, assertState, buildRunReport, compactIdentity, exaTools, fileTools, identityFromEnv, identityTools, lastText, loadProject, memoryDir, readProjectConfig, renderReportHtml, sandboxTools, turns, } from '@zenera/neo';
1
+ import { AgentRunner, FilePayloadStore, MemoryError, SANDBOX_MOUNT, SKILLS_MOUNT, Workspace, assertState, buildRunReport, compactIdentity, exaTools, fileTools, identityFromEnv, identityTools, lastText, loadProject, memoryDir, readProjectConfig, renderReportHtml, sandboxTools, turns, } from '@zenera/neo';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { writeFile } from 'node:fs/promises';
4
4
  import { resolve } from 'node:path';
@@ -8,8 +8,8 @@ import { loadProjectEnv } from "./env.js";
8
8
  import { EXIT, EngineError, invalidError, stderrLogger, usageError, } from "./errors.js";
9
9
  import { readJson, writeJson } from "./home.js";
10
10
  import { KeyStore, assertUsable } from "./keys.js";
11
- import { projectMounts } from "./projects.js";
12
11
  import { envPlugins, loadPlugins, mergePlugins, } from "./plugins.js";
12
+ import { projectMounts } from "./projects.js";
13
13
  import { buildSandbox, prepareHome, teardown, usesSandbox, } from "./sandbox.js";
14
14
  import { acquire, createRun, readSessionMeta, writeRunMeta, writeSessionMeta, } from "./session.js";
15
15
  /**
@@ -21,6 +21,9 @@ export async function openEngine(opts) {
21
21
  const logger = opts.logger ?? stderrLogger;
22
22
  const onIssue = opts.onIssue ?? ((issue) => logger.warn(describeIssue(issue)));
23
23
  const user = resolveUser(opts.user);
24
+ // The machine's plugins are imported before the project's env file is read: a plugin may
25
+ // configure itself from the environment when imported, and that must be the host's.
26
+ const machinePlugins = opts.envPlugins === false ? [] : await envPlugins(logger);
24
27
  // Before the keyring, so the project's own file wins over the machine's
25
28
  // keys, and before the load, so `${VAR}` in agents.yaml can name anything
26
29
  // in it. The real environment still beats both.
@@ -29,7 +32,7 @@ export async function openEngine(opts) {
29
32
  logger.warn(`${env.path} sets ${env.refused.join(', ')} — ignored: who a run acts for, and ` +
30
33
  'which plugins load, are the host’s to say (zen run --as <user> to try one)');
31
34
  }
32
- const hostPlugins = mergePlugins(opts.plugins ?? [], opts.envPlugins === false ? [] : await envPlugins(logger));
35
+ const hostPlugins = mergePlugins(opts.plugins ?? [], machinePlugins);
33
36
  const keys = await KeyStore.open();
34
37
  keys.materialize();
35
38
  assertUsable(keys);
@@ -115,7 +118,7 @@ export async function openEngine(opts) {
115
118
  }
116
119
  catch (err) {
117
120
  await plugins?.close();
118
- throw invalidError(err instanceof Error ? err.message : String(err), `while loading ${opts.project.dir}`);
121
+ throw invalidError(err instanceof Error ? err.message : String(err), err instanceof MemoryError && err.hint ? err.hint : `while loading ${opts.project.dir}`);
119
122
  }
120
123
  const loaded = plugins;
121
124
  let state;
@@ -272,6 +275,35 @@ export async function runEngine(engine, input, onEvent, signal) {
272
275
  }
273
276
  return outcome;
274
277
  }
278
+ /**
279
+ * Continues a session whose last run stopped `suspended`: `answers` answer its
280
+ * suspended calls (re-run, a result, or cancelled - see `ResumeAnswer`). The
281
+ * run is recorded like any other turn; its input reads as the answers given.
282
+ * Calls left unanswered keep it suspended.
283
+ */
284
+ export async function resumeEngine(engine, answers, onEvent, signal) {
285
+ if (!engine.state?.suspended?.length) {
286
+ throw usageError('this session is not waiting on anything to resume');
287
+ }
288
+ const startedAt = new Date();
289
+ const stream = engine.runner.resume(engine.state, { answers, signal });
290
+ if (onEvent) {
291
+ for await (const event of stream) {
292
+ onEvent(event);
293
+ }
294
+ }
295
+ const result = await stream.final();
296
+ const durationMs = Date.now() - startedAt.getTime();
297
+ engine.state = result.state;
298
+ const input = `(resumed: ${Object.entries(answers)
299
+ .map(([id, a]) => `${id} ${a.status}`)
300
+ .join(', ')})`;
301
+ const outcome = await record(engine, input, result, startedAt, durationMs);
302
+ if (result.stopReason === 'failed') {
303
+ throw new EngineError(result.state.error ?? 'the run failed', EXIT.failed, `report: ${outcome.run.report}`);
304
+ }
305
+ return outcome;
306
+ }
275
307
  /**
276
308
  * Everything a finished turn leaves behind. The session state is written first:
277
309
  * if the report fails to render, the conversation is still resumable, which is
package/dist/plugins.d.ts CHANGED
@@ -11,7 +11,8 @@ export interface PluginContext {
11
11
  dir: string;
12
12
  name: string;
13
13
  };
14
- session: SessionPaths;
14
+ /** the session the run belongs to; absent when tools are only listed (`zen check`) */
15
+ session?: SessionPaths;
15
16
  logger: Logger;
16
17
  }
17
18
  export interface ZenPlugin {
@@ -45,4 +46,13 @@ export declare function envPlugins(logger: Logger): Promise<ZenPlugin[]>;
45
46
  export declare function importPlugins(list: string | undefined, logger: Logger): Promise<ZenPlugin[]>;
46
47
  /** The host's plugins and the machine's, the host's winning where both name one. */
47
48
  export declare function mergePlugins(given: readonly ZenPlugin[], machine: readonly ZenPlugin[]): ZenPlugin[];
49
+ /**
50
+ * The tools the machine's plugins add, for checking a project rather than
51
+ * running it: the plugins are asked with no user and no session, and closed
52
+ * again. A plugin that fails or clashes is a warning, and adds nothing.
53
+ */
54
+ export declare function machinePluginTools(project: {
55
+ dir: string;
56
+ name: string;
57
+ }, builtins: readonly AnyTool[], logger: Logger): Promise<AnyTool[]>;
48
58
  //# sourceMappingURL=plugins.d.ts.map
package/dist/plugins.js CHANGED
@@ -137,4 +137,26 @@ export function mergePlugins(given, machine) {
137
137
  const names = new Set(given.map((p) => p.name));
138
138
  return [...given, ...machine.filter((p) => !names.has(p.name))];
139
139
  }
140
+ /**
141
+ * The tools the machine's plugins add, for checking a project rather than
142
+ * running it: the plugins are asked with no user and no session, and closed
143
+ * again. A plugin that fails or clashes is a warning, and adds nothing.
144
+ */
145
+ export async function machinePluginTools(project, builtins, logger) {
146
+ const tools = [];
147
+ for (const plugin of await envPlugins(logger)) {
148
+ try {
149
+ const loaded = await loadPlugins([plugin], { project, logger }, [
150
+ ...builtins,
151
+ ...tools,
152
+ ]);
153
+ await loaded.close();
154
+ tools.push(...loaded.tools);
155
+ }
156
+ catch (err) {
157
+ logger.warn(`${PLUGINS_ENV}: ${message(err)} — its tools are left out`);
158
+ }
159
+ }
160
+ return tools;
161
+ }
140
162
  //# sourceMappingURL=plugins.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/engine",
3
- "version": "1.1.53",
3
+ "version": "1.1.55",
4
4
  "description": "Runs a @zenera/neo project on this machine: keyring, sandbox, sessions and the record each turn leaves behind.",
5
5
  "keywords": [
6
6
  "agents",
@@ -45,6 +45,6 @@
45
45
  "prepack": "tsc -b"
46
46
  },
47
47
  "dependencies": {
48
- "@zenera/neo": "^1.1.53"
48
+ "@zenera/neo": "^1.1.55"
49
49
  }
50
50
  }