agents-relay 2.0.43 → 2.0.45

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
@@ -227,7 +227,7 @@ Every model-backed task declares a durable output with `--output task-pr` or `--
227
227
 
228
228
  For Codex and ChatGPT, **correlated worker events are authoritative lifecycle inputs**, not a separate durable store. Event publication uses the canonical Neo `events-bus` surface (`events__publish` for sandboxed/hosted workers, or `NEO_EVENTS_EMIT` for direct local workers); Agents Relay subscribes, validates correlation, applies graph transitions, persists results in RelayStore, and projects them to GitHub. Relay publishes `task.process.launched` only after successful adapter launch; `task.started` is worker-owned and must be a new, correlated start acknowledgement. Codex publishes `task.process.exited` when its synchronous runtime exits, while ChatGPT publishes `task.process.async_exited` after its submission runtime exits; neither substitutes for `task.completed`, `task.failed`, `task.blocked`, or `task.cancelled`. The ChatGPT worker-owned tab follows the configured close policy: `after-start` waits for the correlated start acknowledgement, `never` keeps it open for debug, and `after-terminal` waits for a new exact-task terminal event; start-ack failure closes only the owned tab after 60 seconds. The executing agent must publish exactly one correlated terminal event. Relay persists the matching durable state only from that terminal event. Model-backed tasks require an event bus and time out explicitly if no terminal event arrives.
229
229
 
230
- The ChatGPT adapter uses the `chatgpt-browser-worker` path selected by the shared execution-context resolver; Relay does not bundle its own copy. The resolver applies project-local > Neo shared > global `~/.agents` precedence at execution time, so durable tasks store names/intent rather than stale absolute skill paths. It opens a fresh Temporary Chat tab, uses the account defaults, submits the prompt, verifies acceptance, and applies `--chatgpt-tab-close-policy` (or `AGENTS_RELAY_CHATGPT_TAB_CLOSE_POLICY`): `after-start` by default, `never` for test/debug, or `after-terminal` after a new exact-task terminal event. It never closes a user tab and does not poll for the assistant response, resume/reopen a thread, or use conversation text as the result channel. Any observed ChatGPT thread ID is diagnostic only.
230
+ The ChatGPT adapter uses Relay's `agents/chatgpt-worker.agent.md` lifecycle contract and Browser Workspace `platforms/chatgpt` for all ChatGPT browser mechanics. It opens a fresh Temporary Chat tab, uses the account defaults, submits the prompt, verifies acceptance, and applies `--chatgpt-tab-close-policy` (or `AGENTS_RELAY_CHATGPT_TAB_CLOSE_POLICY`): `after-start` by default, `never` for test/debug, or `after-terminal` after a new exact-task terminal event. It never closes a user tab and does not poll for the assistant response, resume/reopen a thread, or use conversation text as the result channel. Any observed ChatGPT thread ID is diagnostic only.
231
231
 
232
232
  ~~~sh
233
233
  npx agents-relay task create --repo OWNER/REPO --pr 5 --id job-1 \
package/dist/adapters.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { access, readFile, writeFile, unlink } from 'node:fs/promises';
3
- import { dirname, join } from 'node:path';
2
+ import { readFile, writeFile, unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  export function buildCodexArgs(route, input) {
6
6
  const args = ['exec', '--json'];
@@ -132,24 +132,6 @@ export class LaunchFailure extends Error {
132
132
  }
133
133
  export class StartAckTimeout extends LaunchFailure {
134
134
  }
135
- async function loadBrowserWorkerRuntime(context, explicit) {
136
- const resolvedSkill = context?.skills.find(skill => skill.name === 'chatgpt-browser-worker');
137
- // Once a worker receives a resolved execution context, that context owns
138
- // skill discovery. The explicit path remains the fallback for direct
139
- // adapter use, where no shared context has been resolved yet.
140
- const candidate = resolvedSkill
141
- ? join(dirname(resolvedSkill.path), 'agents', 'browser-worker.agent.md')
142
- : explicit;
143
- if (!candidate)
144
- throw new Error('chatgpt-browser-worker is not visible in the resolved execution context');
145
- try {
146
- await access(candidate);
147
- return { definition: await readFile(candidate, 'utf8'), root: dirname(dirname(candidate)) };
148
- }
149
- catch {
150
- throw new Error(`chatgpt-browser-worker agent definition not found at resolved path: ${candidate}`);
151
- }
152
- }
153
135
  function parseDriverJson(output) {
154
136
  const lines = output.trim().split(/\r?\n/).filter(Boolean);
155
137
  for (let index = lines.length - 1; index >= 0; index -= 1) {
@@ -200,7 +182,7 @@ function parseWorkspaceJson(output) {
200
182
  export async function ensureChatGptBrowserWorkspace(command = process.env.AGENTS_RELAY_BROWSER_WORKSPACE_COMMAND) {
201
183
  await browserWorkspaceCommand(browserWorkspaceCli(command), ['create', CHATGPT_BROWSER_WORKSPACE, String(CHATGPT_BROWSER_WORKSPACE_POOL_SIZE)]);
202
184
  }
203
- function sessionBrowserWorkerRunner(root) {
185
+ function sessionBrowserWorkerRunner() {
204
186
  const cli = browserWorkspaceCli();
205
187
  return (_definition, request, _route, signal) => {
206
188
  const policy = request.tabClosePolicy ?? 'after-start';
@@ -223,16 +205,10 @@ function sessionBrowserWorkerRunner(root) {
223
205
  const promise = (async () => {
224
206
  const configFile = join(process.env.TMPDIR ?? '/tmp', `agents-relay-chatgpt-${randomUUID()}.json`);
225
207
  try {
226
- const [readiness, submitSource, temporarySource] = await Promise.all([
227
- readFile(join(root, 'scripts', '_submit_readiness.py'), 'utf8'),
228
- readFile(join(root, 'scripts', '_submit_bh.py'), 'utf8'),
229
- readFile(join(root, 'scripts', '_temporary_bh.py'), 'utf8'),
230
- ]);
231
208
  await writeFile(configFile, JSON.stringify({
232
209
  prompt: request.prompt,
233
210
  file: request.files ?? [],
234
- close_policy: policy,
235
- release_file: '',
211
+ close_policy: 'never',
236
212
  }));
237
213
  const startOutput = await browserWorkspaceCommand(cli, ['session', 'start', '--workspace', CHATGPT_BROWSER_WORKSPACE]);
238
214
  const started = parseWorkspaceJson(startOutput);
@@ -242,12 +218,7 @@ function sessionBrowserWorkerRunner(root) {
242
218
  activeSessionId = started.session_id;
243
219
  if (signal.aborted)
244
220
  throw new Error('browser-worker execution aborted');
245
- const escapedConfig = configFile.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
246
- const source = [
247
- readiness,
248
- submitSource.replace(/^from browser_harness import \*\s*$/m, ''),
249
- temporarySource.replace(/^_wait_for_release\(\)\s*$/m, ''),
250
- ].join('\n\n').replaceAll('__CFG_PATH__', escapedConfig);
221
+ const source = await browserWorkspaceCommand(cli, ['platform', 'source', 'chatgpt', 'temporary-submit', '--config', configFile]);
251
222
  const execOutput = await browserWorkspaceCommand(cli, ['session', 'exec', activeSessionId], source);
252
223
  const wrapper = parseWorkspaceJson(execOutput);
253
224
  if (wrapper.ok !== true) {
@@ -316,11 +287,9 @@ export class ChatGptAdapter {
316
287
  name = 'chatgpt';
317
288
  id = 'chatgpt/browser-worker';
318
289
  capabilities = ['model', 'chatgpt', 'browser-workspace'];
319
- agentPath;
320
290
  runner;
321
291
  tabClosePolicy;
322
292
  constructor(options = {}) {
323
- this.agentPath = options.agentPath ?? process.env.AGENTS_RELAY_CHATGPT_BROWSER_WORKER_AGENT;
324
293
  this.runner = options.agentRunner;
325
294
  this.tabClosePolicy = chatGptTabClosePolicy(options.tabClosePolicy ?? process.env.AGENTS_RELAY_CHATGPT_TAB_CLOSE_POLICY);
326
295
  }
@@ -344,11 +313,11 @@ export class ChatGptAdapter {
344
313
  execution.launched = new Promise((resolve, reject) => { launchResolve = resolve; launchReject = reject; });
345
314
  execution.launched.catch(() => { });
346
315
  execution.promise = (async () => {
347
- const runtime = await loadBrowserWorkerRuntime(context.executionContext, this.agentPath);
316
+ const definition = await readFile(new URL('../agents/chatgpt-worker.agent.md', import.meta.url), 'utf8');
348
317
  if (controller.signal.aborted)
349
318
  throw new Error('browser-worker execution aborted');
350
319
  const policy = chatGptTabClosePolicy(task.chatgptTabClosePolicy, this.tabClosePolicy);
351
- const run = (this.runner ?? sessionBrowserWorkerRunner(runtime.root))(runtime.definition, { prompt: task.input, ...(task.attachments?.length ? { files: task.attachments } : {}), tabClosePolicy: policy }, route, controller.signal);
320
+ const run = (this.runner ?? sessionBrowserWorkerRunner())(definition, { prompt: task.input, ...(task.attachments?.length ? { files: task.attachments } : {}), tabClosePolicy: policy }, route, controller.signal);
352
321
  try {
353
322
  const output = await (run.submitted ?? run.promise);
354
323
  const response = parseBrowserWorkerResponse(output);
@@ -26,7 +26,7 @@ Retrieve this task's lifecycle event history before classifying the root cause o
26
26
  GET http://127.0.0.1:8787/api/events?job=${encodeURIComponent(job.id)}&task=${encodeURIComponent(task.id)}&limit=200
27
27
  Inspect event order, type/status, source, data/phase, duplicate events, missing expected lifecycle events, late events, and execution identifiers. If the event stream points to delivery/runtime failure, inspect the corresponding Relay/worker/tool logs too. Do not treat the final task.error string as the complete root cause.
28
28
 
29
- Follow the selected Troubleshooter agent contract. Diagnose root cause from durable task state + event history + relevant runtime logs before retry. The final task.error string alone is never sufficient. Classify the evidence as model/provider/runtime, worker/tool/infrastructure, semantic task failure, or timeout/unknown. Only model/provider/runtime evidence may call the narrow model re-decision action; that action gathers fresh usage, reloads current policy, updates this exact Task's routing, and does not retry it. Troubleshooter must then reactivate/retry this exact original task and verify progress. Do not manually choose a provider/model or create a replacement task. If the blocker is a shared worker/runtime/tool defect, create and directly complete a P0/self-job in the owning repository, including required release/deploy verification. Retry this exact original task only after the blocker is removed, reconcile the original job, and verify durable progress.`;
29
+ Follow the selected Troubleshooter agent contract. Diagnose root cause from durable task state + event history + relevant runtime logs before retry. The final task.error string alone is never sufficient. Classify the evidence as model/provider/runtime, worker/tool/infrastructure, semantic task failure, or timeout/unknown. Only model/provider/runtime evidence may call the narrow model re-decision action; that action gathers fresh usage, reloads current policy, updates this exact Task's routing, and does not retry it. Troubleshooter must then reactivate/retry this exact original task and verify progress. Do not manually choose a provider/model or create a replacement task. If the blocker is a shared worker/runtime/tool defect, create and directly complete a P0/self-job in the owning repository, including required release/deploy verification. If diagnosis suggests a genuine human/external blocker, do not publish task.blocked directly: first call the Neo CLI neo escalation with the original intent, blocker, and relevant context. Pass any caller-defined notification plan through --on-needs-user/--on-needs-user-file; do not hard-code a channel. Continue recovery on resolved; treat failed as technical recovery evidence; only needs_user justifies publishing task.blocked with the returned exact user action and notification evidence. Retry this exact original task only after the blocker is removed, reconcile the original job, and verify durable progress.`;
30
30
  }
31
31
  function belongsToCurrentRun(job, task) {
32
32
  const slot = job.schedule?.currentRunSlot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.43",
3
+ "version": "2.0.45",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "scripts": {
11
11
  "build": "tsc -p tsconfig.json && chmod +x dist/cli.js dist/relayd.js dist/relayd-main.js",
12
12
  "test": "npm run build && node --test test/*.test.js",
13
- "test:chatgpt-temp-quality": "AGENTS_RELAY_RUN_CHATGPT_TEMP_QUALITY=1 AGENTS_RELAY_CHATGPT_BROWSER_WORKER_AGENT=/Users/chengli/Workspace/neo/skills/chatgpt-browser-worker/agents/browser-worker.agent.md sh -c \"npm run build && node test/quality/run-temporary-chat.mjs\"",
13
+ "test:chatgpt-temp-quality": "AGENTS_RELAY_RUN_CHATGPT_TEMP_QUALITY=1 sh -c \"npm run build && node test/quality/run-temporary-chat.mjs\"",
14
14
  "dev": "tsx src/cli.ts",
15
15
  "test:publish": "npm run build && node --test --test-force-exit test/*.test.js"
16
16
  },
@@ -91,7 +91,7 @@ Leaf workers consume the resolved instruction/skill/agent paths, not the full to
91
91
 
92
92
  ## ChatGPT worker
93
93
 
94
- The ChatGPT adapter uses the `chatgpt-browser-worker` path from the shared execution context; Relay does not bundle or independently rediscover that skill. It opens a fresh Temporary Chat tab, submits the complete task with the account defaults, verifies acceptance, applies the configured `after-start` (default), `never`, or `after-terminal` owned-tab close policy, and returns a submission receipt.
94
+ The ChatGPT adapter uses Relay's `chatgpt-worker` agent contract and Browser Workspace ChatGPT platform; it does not depend on a Neo worker skill. It opens a fresh Temporary Chat tab, submits the complete task with the account defaults, verifies acceptance, applies the configured `after-start` (default), `never`, or `after-terminal` owned-tab close policy, and returns a submission receipt.
95
95
 
96
96
  ChatGPT thread identity is diagnostic only. It is not persisted as a resumable task handle, and Relay does not poll, resume, retrieve a result from, or delete a ChatGPT conversation as part of task lifecycle.
97
97
 
@@ -29,7 +29,10 @@ Read and follow the shared Task / Job contract and orchestrator P0/self-job work
29
29
  - For a transient failure, verify the transient condition is gone.
30
30
  - For task-local failure, make the smallest durable repair in the original Job/PR when that is within scope.
31
31
  - For a shared platform/runtime defect, first create or reuse the owning blocker P0/self-job. If it can be completed inside this recovery execution, fix/verify it and retry the original Task. If it cannot be completed now, register the original Task with `agents-relay blocker add` on that blocker Job and finish this Troubleshooter task; do not stay running while waiting for another Job.
32
- - For a genuine human/external blocker, do not fake progress. Publish task.blocked with the exact action required.
32
+ - For a suspected genuine human/external blocker, do not publish `task.blocked` yet. First invoke the Neo CLI `neo escalation` with the original intent, blocker, and relevant context. If the recovery context defines a preferred way to contact the user, pass that description through `--on-needs-user` or `--on-needs-user-file`; Relay must not hard-code a channel. Handle the structured result:
33
+ - `resolved`: the escalation agent solved the blocker; continue the normal retry/reconcile path and verify durable progress.
34
+ - `failed`: this remains a technical recovery outcome; preserve the evidence and do not relabel it as human-required.
35
+ - `needs_user`: only now publish `task.blocked`, using the returned `user_action` and notification evidence.
33
36
  6. For an immediate repair, only after the blocker is removed run `agents-relay task retry` for the original Task and reconcile its Job. For deferred repair, rely on the durable blocker relation: merge-mode blockers wake matching dependents after merge; release/deploy/runtime blockers use `--resolution verified` and `agents-relay blocker resolve` only after verification.
34
37
  7. Verify durable evidence that the original Task moved: at minimum QUEUED after retry, preferably a new execution / task.started / observable progress.
35
38
  8. Publish this Troubleshooter Task's terminal event only after the recovery outcome is durable.