@zenera/engine 1.1.54 → 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
  /**
@@ -118,7 +118,7 @@ export async function openEngine(opts) {
118
118
  }
119
119
  catch (err) {
120
120
  await plugins?.close();
121
- 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}`);
122
122
  }
123
123
  const loaded = plugins;
124
124
  let state;
@@ -275,6 +275,35 @@ export async function runEngine(engine, input, onEvent, signal) {
275
275
  }
276
276
  return outcome;
277
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
+ }
278
307
  /**
279
308
  * Everything a finished turn leaves behind. The session state is written first:
280
309
  * if the report fails to render, the conversation is still resumable, which is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/engine",
3
- "version": "1.1.54",
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.54"
48
+ "@zenera/neo": "^1.1.55"
49
49
  }
50
50
  }