agents-relay 2.0.57 → 2.0.58
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 +3 -3
- package/agents/chatgpt-worker.agent.md +4 -6
- package/dist/adapters.js +16 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -230,9 +230,9 @@ Managed tasks are descriptive work for agents, not shell commands. Use `codex` o
|
|
|
230
230
|
|
|
231
231
|
Every model-backed task declares a durable output with `--output task-pr` or `--output file --output-path PATH`. Agents Relay bootstraps workers with the managed PR URL, durable task-comment URL, job/task identity, project, output contract, and mandatory terminal-event contract; workers retrieve the task intent from GitHub rather than receiving a duplicate copy of the stored task prompt.
|
|
232
232
|
|
|
233
|
-
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
|
|
233
|
+
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 one-shot submission runtime exits; neither substitutes for `task.completed`, `task.failed`, `task.blocked`, or `task.cancelled`. 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.
|
|
234
234
|
|
|
235
|
-
The ChatGPT adapter uses Relay's `agents/chatgpt-worker.agent.md` lifecycle contract and Browser Workspace `
|
|
235
|
+
The ChatGPT adapter uses Relay's `agents/chatgpt-worker.agent.md` lifecycle contract and invokes Browser Workspace exactly as a one-shot platform action: `browser-workspace platform run chatgpt temporary-submit --auto-session --config <file>`. Browser Workspace owns creation/release of the fresh Temporary Chat session, selectors, attachments, and submit verification; Relay does not self-heal browser failures and instead lets delivery failures enter its normal retry/recovery lifecycle. Legacy `--chatgpt-tab-close-policy` values remain accepted for compatibility around Relay's event barriers, but they do not control the Browser Workspace auto-session lifetime. The adapter never uses a pre-existing user tab, polls for the assistant response, resumes/reopens a thread, or treats conversation text as the result channel. Any observed ChatGPT thread ID is diagnostic only.
|
|
236
236
|
|
|
237
237
|
~~~sh
|
|
238
238
|
npx agents-relay task create --repo OWNER/REPO --pr 5 --id job-1 \
|
|
@@ -283,4 +283,4 @@ For autonomous Jobs, `UNBLOCKED` is historical: Relay does not retry that old Ta
|
|
|
283
283
|
|
|
284
284
|
### Browser Workspace product boundary
|
|
285
285
|
|
|
286
|
-
Agents Relay owns the persistent `Relay` browser workspace as product infrastructure. At daemon startup it runs `browser-workspace status`, dynamically imports the returned absolute `admin_helper`, and calls `ensureWorkspace("Relay", 6)`. This bootstrap is best-effort: Browser Workspace discovery/helper failures are logged and skipped so they do not prevent `agents-relayd` from starting; ChatGPT browser work may remain unavailable until Browser Workspace recovers. Ordinary worker
|
|
286
|
+
Agents Relay owns the persistent `Relay` browser workspace as product infrastructure. At daemon startup it runs `browser-workspace status`, dynamically imports the returned absolute `admin_helper`, and calls `ensureWorkspace("Relay", 6)`. This bootstrap is best-effort: Browser Workspace discovery/helper failures are logged and skipped so they do not prevent `agents-relayd` from starting; ChatGPT browser work may remain unavailable until Browser Workspace recovers. Ordinary ChatGPT worker activity uses the public Browser Workspace one-shot `platform run ... --auto-session` command; orchestrator-owned self-healing flows may still use explicit session commands. Relay does not create or delete workspaces through CLI lifecycle commands.
|
|
@@ -11,14 +11,12 @@ You execute one complete Relay task by submitting it to a fresh worker-owned Cha
|
|
|
11
11
|
## Contract
|
|
12
12
|
|
|
13
13
|
- Browser Workspace `platforms/chatgpt` is the sole owner of ChatGPT selectors, readiness checks, attachments, Temporary Chat mechanics, and submit verification.
|
|
14
|
-
- Relay owns worker lifecycle, task/event barriers, routing, cancellation
|
|
15
|
-
- Execute only a one-shot submission
|
|
16
|
-
-
|
|
14
|
+
- Relay owns worker lifecycle, task/event barriers, routing, and cancellation; Browser Workspace owns the one-shot auto-session lifecycle.
|
|
15
|
+
- Execute only a one-shot submission with `platform run chatgpt temporary-submit --auto-session`. Do not expose a create/resume/status/result conversation lifecycle.
|
|
16
|
+
- Let Browser Workspace create and release the fresh Temporary Chat session/tab for the action. Never submit through a pre-existing user tab.
|
|
17
17
|
- Preserve the account's current default model/thinking settings.
|
|
18
18
|
- A submission is accepted only after Browser Workspace returns a verified submit receipt.
|
|
19
19
|
- Treat observed conversation/thread identity as diagnostic only, never as a resumable handle.
|
|
20
|
-
-
|
|
21
|
-
- `after-terminal` releases it after the exact task terminal barrier.
|
|
22
|
-
- `never` leaves the owned session open for test/debug.
|
|
20
|
+
- Legacy ChatGPT tab-close policy values remain accepted by Relay for compatibility, but they do not control Browser Workspace's auto-session lifetime or add self-heal.
|
|
23
21
|
- Do not wait for the assistant response. Durable task success/failure is event-owned by Relay and the executing task.
|
|
24
22
|
- Stop on authentication, MFA, consent, or ambiguous browser state rather than bypassing it.
|
package/dist/adapters.js
CHANGED
|
@@ -151,20 +151,28 @@ function parseDriverJson(output) {
|
|
|
151
151
|
function browserWorkspaceCli(override = process.env.AGENTS_RELAY_BROWSER_WORKSPACE_COMMAND) {
|
|
152
152
|
return override ? { command: override, prefix: [] } : { command: 'npx', prefix: ['browser-workspace'] };
|
|
153
153
|
}
|
|
154
|
-
function browserWorkspaceCommand(cli, args, stdin) {
|
|
154
|
+
function browserWorkspaceCommand(cli, args, stdin, signal) {
|
|
155
155
|
return new Promise((resolve, reject) => {
|
|
156
156
|
const child = spawn(cli.command, [...cli.prefix, ...args], { shell: false, stdio: [stdin === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'] });
|
|
157
157
|
let stdout = '';
|
|
158
158
|
let stderr = '';
|
|
159
|
+
const abort = () => terminateChild(child);
|
|
159
160
|
child.stdout?.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
160
161
|
child.stderr?.on('data', (chunk) => { stderr = appendTail(stderr, chunk.toString()); });
|
|
161
162
|
child.on('error', reject);
|
|
162
163
|
child.on('close', code => {
|
|
164
|
+
signal?.removeEventListener('abort', abort);
|
|
163
165
|
if (code === 0)
|
|
164
166
|
resolve(stdout.trim());
|
|
167
|
+
else if (signal?.aborted)
|
|
168
|
+
reject(new Error('browser-worker execution aborted'));
|
|
165
169
|
else
|
|
166
170
|
reject(new Error(stderr.trim() || stdout.trim() || `browser-workspace exited ${code}`));
|
|
167
171
|
});
|
|
172
|
+
if (signal?.aborted)
|
|
173
|
+
abort();
|
|
174
|
+
else
|
|
175
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
168
176
|
if (stdin !== undefined) {
|
|
169
177
|
child.stdin?.end(stdin);
|
|
170
178
|
}
|
|
@@ -191,86 +199,26 @@ export async function ensureChatGptBrowserWorkspace(command = process.env.AGENTS
|
|
|
191
199
|
}
|
|
192
200
|
await admin.ensureWorkspace(CHATGPT_BROWSER_WORKSPACE, CHATGPT_BROWSER_WORKSPACE_POOL_SIZE);
|
|
193
201
|
}
|
|
194
|
-
function
|
|
202
|
+
function autoSessionBrowserWorkerRunner() {
|
|
195
203
|
const cli = browserWorkspaceCli();
|
|
196
204
|
return (_definition, request, _route, signal) => {
|
|
197
|
-
const policy = request.tabClosePolicy ?? 'after-start';
|
|
198
|
-
let activeSessionId;
|
|
199
|
-
let released = false;
|
|
200
|
-
let releaseResolve;
|
|
201
|
-
const releaseBarrier = new Promise(resolve => { releaseResolve = resolve; });
|
|
202
|
-
const release = () => {
|
|
203
|
-
if (released)
|
|
204
|
-
return;
|
|
205
|
-
released = true;
|
|
206
|
-
releaseResolve();
|
|
207
|
-
};
|
|
208
|
-
let submittedResolve;
|
|
209
|
-
let submittedReject;
|
|
210
|
-
const submitted = new Promise((resolve, reject) => {
|
|
211
|
-
submittedResolve = resolve;
|
|
212
|
-
submittedReject = reject;
|
|
213
|
-
});
|
|
214
205
|
const promise = (async () => {
|
|
215
206
|
const configFile = join(process.env.TMPDIR ?? '/tmp', `agents-relay-chatgpt-${randomUUID()}.json`);
|
|
216
207
|
try {
|
|
217
208
|
await writeFile(configFile, JSON.stringify({
|
|
218
209
|
prompt: request.prompt,
|
|
219
210
|
file: request.files ?? [],
|
|
220
|
-
close_policy: 'never',
|
|
221
211
|
}));
|
|
222
|
-
const startOutput = await browserWorkspaceCommand(cli, ['session', 'start', '--workspace', CHATGPT_BROWSER_WORKSPACE]);
|
|
223
|
-
const started = parseWorkspaceJson(startOutput);
|
|
224
|
-
if (typeof started.session_id !== 'string') {
|
|
225
|
-
throw new Error('browser-workspace session start returned no session_id');
|
|
226
|
-
}
|
|
227
|
-
activeSessionId = started.session_id;
|
|
228
212
|
if (signal.aborted)
|
|
229
213
|
throw new Error('browser-worker execution aborted');
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
const wrapper = parseWorkspaceJson(execOutput);
|
|
233
|
-
if (wrapper.ok !== true) {
|
|
234
|
-
throw new Error(typeof wrapper.error === 'string' ? wrapper.error : 'browser-workspace session exec failed');
|
|
235
|
-
}
|
|
236
|
-
const receipt = parseDriverJson(typeof wrapper.stdout === 'string' ? wrapper.stdout : '');
|
|
237
|
-
const result = JSON.stringify(receipt);
|
|
238
|
-
submittedResolve(result);
|
|
239
|
-
if (policy === 'never') {
|
|
240
|
-
await unlink(configFile).catch(() => { });
|
|
241
|
-
activeSessionId = undefined;
|
|
242
|
-
return result;
|
|
243
|
-
}
|
|
244
|
-
await releaseBarrier;
|
|
245
|
-
if (activeSessionId) {
|
|
246
|
-
await browserWorkspaceCommand(cli, ['session', 'stop', activeSessionId]);
|
|
247
|
-
activeSessionId = undefined;
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
214
|
+
const output = await browserWorkspaceCommand(cli, ['platform', 'run', 'chatgpt', 'temporary-submit', '--auto-session', '--config', configFile], undefined, signal);
|
|
215
|
+
return JSON.stringify(parseDriverJson(output));
|
|
250
216
|
}
|
|
251
|
-
|
|
252
|
-
if (activeSessionId) {
|
|
253
|
-
await browserWorkspaceCommand(cli, ['session', 'stop', activeSessionId]).catch(() => { });
|
|
254
|
-
activeSessionId = undefined;
|
|
255
|
-
}
|
|
217
|
+
finally {
|
|
256
218
|
await unlink(configFile).catch(() => { });
|
|
257
|
-
const error = failure instanceof Error ? failure : new Error(String(failure));
|
|
258
|
-
submittedReject(error);
|
|
259
|
-
throw error;
|
|
260
219
|
}
|
|
261
220
|
})();
|
|
262
|
-
|
|
263
|
-
release();
|
|
264
|
-
if (activeSessionId) {
|
|
265
|
-
void browserWorkspaceCommand(cli, ['session', 'stop', activeSessionId]);
|
|
266
|
-
activeSessionId = undefined;
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
if (signal.aborted)
|
|
270
|
-
cancel();
|
|
271
|
-
else
|
|
272
|
-
signal.addEventListener('abort', cancel, { once: true });
|
|
273
|
-
return { promise, submitted, release, cancel };
|
|
221
|
+
return { promise, submitted: promise, cancel: () => { } };
|
|
274
222
|
};
|
|
275
223
|
}
|
|
276
224
|
function parseBrowserWorkerResponse(output) {
|
|
@@ -326,7 +274,7 @@ export class ChatGptAdapter {
|
|
|
326
274
|
if (controller.signal.aborted)
|
|
327
275
|
throw new Error('browser-worker execution aborted');
|
|
328
276
|
const policy = chatGptTabClosePolicy(task.chatgptTabClosePolicy, this.tabClosePolicy);
|
|
329
|
-
const run = (this.runner ??
|
|
277
|
+
const run = (this.runner ?? autoSessionBrowserWorkerRunner())(definition, { prompt: task.input, ...(task.attachments?.length ? { files: task.attachments } : {}), tabClosePolicy: policy }, route, controller.signal);
|
|
330
278
|
try {
|
|
331
279
|
const output = await (run.submitted ?? run.promise);
|
|
332
280
|
const response = parseBrowserWorkerResponse(output);
|
|
@@ -335,7 +283,7 @@ export class ChatGptAdapter {
|
|
|
335
283
|
await context.waitForWorkerStart;
|
|
336
284
|
else if (policy === 'after-terminal') {
|
|
337
285
|
if (!context.waitForTerminal)
|
|
338
|
-
throw new Error('ChatGPT after-terminal
|
|
286
|
+
throw new Error('ChatGPT after-terminal compatibility policy requires a terminal event barrier');
|
|
339
287
|
await context.waitForTerminal;
|
|
340
288
|
}
|
|
341
289
|
if (policy !== 'never')
|