@dougbots/avenor-core 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/README.md CHANGED
@@ -67,7 +67,7 @@ Status values: `running`, `done`, `failed`, `timeout`, `killed`. If a permission
67
67
 
68
68
  ### `resultTool`
69
69
 
70
- Waits for one run to finish and returns its bounded final output without transcript or event details.
70
+ Waits for one run to finish and returns its complete final output without transcript or event details.
71
71
 
72
72
  ```ts
73
73
  import { resultTool } from '@dougbots/avenor-core'
@@ -138,7 +138,7 @@ const { run_id } = await spawnTool({
138
138
  label: 'security-review',
139
139
  })
140
140
 
141
- // 3. Wait for the bounded final output
141
+ // 3. Wait for the complete final output
142
142
  const result = await resultTool({ runId: run_id })
143
143
  console.log(result) // { run_id, label, status: 'done', ready: true, output: '...' }
144
144
 
@@ -156,7 +156,7 @@ await shutdownTool()
156
156
  | `findAvenorBinary` | function | Discover the avenor binary path |
157
157
  | `spawnTool` | function | Start a new agent run |
158
158
  | `statusTool` | function | Query run lifecycle status (single or all) |
159
- | `resultTool` | function | Wait for and retrieve one run's bounded final output |
159
+ | `resultTool` | function | Wait for and retrieve one run's complete final output |
160
160
  | `answerPermissionTool` | function | Answer a pending permission request |
161
161
  | `followUpTool` | function | Resume a session with a follow-up prompt |
162
162
  | `eventsTool` | function | Read recent events from a run's event log |
package/dist/index.d.ts CHANGED
@@ -62,6 +62,7 @@ declare class Client {
62
62
  events(options?: SubscribeOptions): AsyncIterableIterator<Event>;
63
63
  history(options: HistoryOptions): Promise<HistoryResult>;
64
64
  status(runtimeId?: string): Promise<Record<string, unknown>>;
65
+ result(runtimeId?: string): Promise<Record<string, unknown>>;
65
66
  list(): Promise<Array<Record<string, unknown>>>;
66
67
  spawn(params: SpawnParams): Promise<Record<string, unknown>>;
67
68
  shutdown(mode: string): Promise<void>;
@@ -232,6 +233,9 @@ interface RunInfo {
232
233
  eventLogPath: string;
233
234
  runtimeId?: string;
234
235
  sessionId?: string;
236
+ agent?: string;
237
+ backend?: string;
238
+ dir?: string;
235
239
  brokerUrl?: string;
236
240
  parentToken?: string;
237
241
  }
@@ -318,6 +322,7 @@ interface StatusResult {
318
322
  usage?: Record<string, unknown>;
319
323
  latest_seq?: number;
320
324
  final_output?: string;
325
+ final_output_truncated?: boolean;
321
326
  }
322
327
  declare function statusTool(args: {
323
328
  runId?: string;
@@ -356,6 +361,8 @@ interface ResultResult {
356
361
  stop_reason?: string;
357
362
  pending_permission?: StatusResult['pending_permission'];
358
363
  output?: string;
364
+ output_truncated?: boolean;
365
+ output_event_path?: string;
359
366
  timed_out?: boolean;
360
367
  }
361
368
  interface ResultToolArgs {
package/dist/index.js CHANGED
@@ -233,6 +233,10 @@ var Client = class {
233
233
  const params = runtimeId ? { runtime_id: runtimeId } : void 0;
234
234
  return this.call("status", params);
235
235
  }
236
+ async result(runtimeId) {
237
+ const params = runtimeId ? { runtime_id: runtimeId } : void 0;
238
+ return this.call("result", params);
239
+ }
236
240
  async list() {
237
241
  return this.call("list");
238
242
  }
@@ -1257,6 +1261,9 @@ var Supervisor = class Supervisor {
1257
1261
  eventLogPath: result.on_event ?? eventLogPath,
1258
1262
  runtimeId: result.runtime_id,
1259
1263
  sessionId: result.session_id,
1264
+ agent: spawnParams.agent ?? params.agent,
1265
+ backend: spawnParams.backend,
1266
+ dir: spawnParams.dir,
1260
1267
  brokerUrl: result.broker_url,
1261
1268
  parentToken: result.parent_token
1262
1269
  };
@@ -1497,7 +1504,8 @@ function buildBaseStatus(source, fallback, translatedStatus) {
1497
1504
  event_path: stringField(source, "event_path", "on_event"),
1498
1505
  usage: asRecord(source.usage) ?? void 0,
1499
1506
  latest_seq: numberField(source, "latest_seq"),
1500
- final_output: stringField(source, "final_output", "finalOutput")
1507
+ final_output: stringField(source, "final_output", "finalOutput"),
1508
+ ...source.final_output_truncated === true && { final_output_truncated: true }
1501
1509
  };
1502
1510
  }
1503
1511
  function shapeStatusResult(result, view = "full") {
@@ -1874,9 +1882,23 @@ function sleep(ms, signal) {
1874
1882
  signal?.addEventListener("abort", onAbort, { once: true });
1875
1883
  });
1876
1884
  }
1885
+ async function fullOutput(runtimeId, supervisorId) {
1886
+ if (supervisorId) {
1887
+ const { client, isSingleton } = await getSupervisorClient(supervisorId);
1888
+ try {
1889
+ const result = await client.result(runtimeId);
1890
+ return typeof result.final_output === "string" ? result.final_output : void 0;
1891
+ } finally {
1892
+ if (!isSingleton) client.close();
1893
+ }
1894
+ }
1895
+ const result = await (await Supervisor.get()).getClient().result(runtimeId);
1896
+ return typeof result.final_output === "string" ? result.final_output : void 0;
1897
+ }
1877
1898
  const defaultDeps = {
1878
1899
  status: statusTool,
1879
1900
  inspect: inspectTool,
1901
+ fullOutput,
1880
1902
  sleep,
1881
1903
  now: Date.now,
1882
1904
  pollIntervalMs: DEFAULT_POLL_INTERVAL_MS
@@ -1893,6 +1915,8 @@ function toResult(status, timedOut = false) {
1893
1915
  stop_reason: status.stop_reason,
1894
1916
  pending_permission: status.pending_permission,
1895
1917
  output: ready ? status.final_output : void 0,
1918
+ output_truncated: ready && status.final_output_truncated ? true : void 0,
1919
+ output_event_path: ready && status.final_output_truncated ? status.event_path : void 0,
1896
1920
  timed_out: timedOut || void 0
1897
1921
  };
1898
1922
  }
@@ -1911,13 +1935,23 @@ async function executeResultTool(args, deps) {
1911
1935
  const status = Array.isArray(raw) ? raw[0] : raw;
1912
1936
  if (!status) throw new Error(`run not found: ${args.runId}`);
1913
1937
  if (TERMINAL_STATUSES.has(status.status)) {
1914
- if (!status.final_output) try {
1938
+ let exactOutputRetrieved = false;
1939
+ try {
1940
+ const output = await deps.fullOutput?.(status.runtime_id, args.supervisorId);
1941
+ if (output !== void 0) {
1942
+ status.final_output = output;
1943
+ status.final_output_truncated = false;
1944
+ exactOutputRetrieved = true;
1945
+ }
1946
+ } catch {}
1947
+ if (status.final_output === void 0) try {
1915
1948
  const inspected = await deps.inspect({
1916
1949
  runId: args.runId,
1917
1950
  supervisorId: args.supervisorId
1918
1951
  });
1919
1952
  status.final_output = inspected.final_output ?? inspected.snapshot.final_output ?? inspected.snapshot.assistant_text;
1920
1953
  } catch {}
1954
+ if (!exactOutputRetrieved && status.final_output !== void 0) status.final_output_truncated = true;
1921
1955
  return toResult(status);
1922
1956
  }
1923
1957
  if (status.status === "waiting" || !wait) return toResult(status);
@@ -1970,6 +2004,10 @@ function findRunByLabel(sup, runId) {
1970
2004
  if (byKey) return byKey;
1971
2005
  for (const info of runs.values()) if (info.label === runId) return info;
1972
2006
  }
2007
+ function requireResumableAgent(agent) {
2008
+ if (agent === void 0) throw new Error("run has no agent to resume");
2009
+ return agent;
2010
+ }
1973
2011
  async function parseSentinel(filePath) {
1974
2012
  try {
1975
2013
  const content = (await fs.promises.readFile(filePath, "utf-8")).trim();
@@ -1990,28 +2028,36 @@ async function parseSentinel(filePath) {
1990
2028
  }
1991
2029
  async function followUpTool(args) {
1992
2030
  if (args.supervisorId) {
1993
- const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
2031
+ const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
1994
2032
  try {
1995
2033
  validateRunId(args.runId);
2034
+ let runInfo;
2035
+ if (isSingleton && sup) runInfo = findRunByLabel(sup, args.runId);
2036
+ const fallbackSessionId = runInfo?.sessionId;
1996
2037
  let liveStatus = null;
1997
2038
  try {
1998
2039
  liveStatus = await client.status(args.runId);
1999
2040
  } catch {}
2000
- const sentinel = await parseSentinel(liveStatus?.sentinel_file ?? path.join(runsRoot(), args.runId, "sentinel.done"));
2001
- if (!sentinel?.SESSION) throw new Error("run has no session to resume");
2002
- const agent = liveStatus?.agent ?? "codex";
2041
+ const sessionId = (await parseSentinel(liveStatus?.sentinel_file ?? path.join(runsRoot(), args.runId, "sentinel.done")))?.SESSION ?? liveStatus?.session_id ?? fallbackSessionId;
2042
+ if (!sessionId) throw new Error("run has no session to resume");
2043
+ const agent = requireResumableAgent(liveStatus?.agent ?? runInfo?.agent);
2044
+ const backend = liveStatus?.backend ?? runInfo?.backend;
2045
+ const dir = liveStatus?.dir ?? runInfo?.dir;
2003
2046
  const followUpRunId = crypto.randomUUID();
2004
2047
  const followUpLabel = args.label ?? `${args.runId}-followup`;
2005
2048
  const { sentinelPath: followUpSentinelPath, eventLogPath } = ensureRunPaths(followUpRunId);
2049
+ const spawnParams = {
2050
+ agent,
2051
+ prompt: args.message,
2052
+ label: followUpLabel,
2053
+ session_id: sessionId,
2054
+ sentinel_file: followUpSentinelPath,
2055
+ on_event: eventLogPath
2056
+ };
2057
+ if (backend) spawnParams.backend = backend;
2058
+ if (dir) spawnParams.dir = dir;
2006
2059
  return {
2007
- run_id: (await client.spawn({
2008
- agent,
2009
- prompt: args.message,
2010
- label: followUpLabel,
2011
- session_id: sentinel.SESSION,
2012
- sentinel_file: followUpSentinelPath,
2013
- on_event: eventLogPath
2014
- })).runtime_id ?? followUpRunId,
2060
+ run_id: (await client.spawn(spawnParams)).runtime_id ?? followUpRunId,
2015
2061
  label: followUpLabel
2016
2062
  };
2017
2063
  } finally {
@@ -2021,18 +2067,23 @@ async function followUpTool(args) {
2021
2067
  const sup = await Supervisor.get();
2022
2068
  const runInfo = findRunByLabel(sup, args.runId);
2023
2069
  if (!runInfo) throw new Error(`run not found: ${args.runId}`);
2024
- const sentinel = await parseSentinel(runInfo.sentinelPath);
2025
- if (!sentinel?.SESSION) throw new Error("run has no session to resume");
2070
+ const sessionId = (await parseSentinel(runInfo.sentinelPath))?.SESSION ?? runInfo.sessionId;
2071
+ if (!sessionId) throw new Error("run has no session to resume");
2026
2072
  const client = sup.getClient();
2027
- let agent = "codex";
2073
+ let liveStatus = null;
2028
2074
  if (runInfo.runtimeId) try {
2029
- agent = (await client.status(runInfo.runtimeId))?.agent ?? "codex";
2075
+ liveStatus = await client.status(runInfo.runtimeId);
2030
2076
  } catch {}
2077
+ const agent = requireResumableAgent(liveStatus?.agent ?? runInfo.agent);
2078
+ const backend = liveStatus?.backend ?? runInfo.backend;
2079
+ const dir = liveStatus?.dir ?? runInfo.dir;
2031
2080
  const followUpLabel = args.label ?? `${runInfo.label}-followup`;
2032
2081
  const followUpRun = await sup.spawn({
2033
2082
  agent,
2083
+ backend,
2084
+ dir,
2034
2085
  prompt: args.message,
2035
- session_id: sentinel.SESSION,
2086
+ session_id: sessionId,
2036
2087
  label: followUpLabel
2037
2088
  });
2038
2089
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-core",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",