agents-relay 2.0.95 → 2.0.97
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/adapters.js
CHANGED
|
@@ -228,63 +228,12 @@ export async function ensureChatGptBrowserWorkspace(command = process.env.AGENTS
|
|
|
228
228
|
}
|
|
229
229
|
await admin.ensureWorkspace(CHATGPT_BROWSER_WORKSPACE, CHATGPT_BROWSER_WORKSPACE_POOL_SIZE);
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
* The upstream new-turn action accepts idempotency_key but does not enforce it.
|
|
233
|
-
* Inspect the current thread before resubmitting after an ambiguous exit.
|
|
234
|
-
* If the transcript is not observable, fail closed rather than duplicate.
|
|
235
|
-
*/
|
|
236
|
-
async function findExistingReviewTurn(cli, sessionId, delivery, signal) {
|
|
237
|
-
const pathname = delivery.projectId
|
|
238
|
-
? `/g/${encodeURIComponent(delivery.projectId)}/c/${encodeURIComponent(delivery.threadId)}`
|
|
239
|
-
: `/c/${encodeURIComponent(delivery.threadId)}`;
|
|
240
|
-
const marker = `Relay delivery ID: ${delivery.requestId}`;
|
|
241
|
-
const inspectJs = `(() => {
|
|
242
|
-
const legacy = [...document.querySelectorAll('[data-message-author-role="user"]')]
|
|
243
|
-
.map(e => ({text: (e.innerText || '').trim(), id: e.getAttribute('data-message-id') || ''})).filter(x => x.text);
|
|
244
|
-
if (legacy.length) return legacy;
|
|
245
|
-
return [...document.querySelectorAll('h4')].filter(e => (e.innerText || '').trim() === 'You said:')
|
|
246
|
-
.map((e,i) => ({text: (e.parentElement?.innerText || '').trim(), id: 'dom-user-' + i})).filter(x => x.text);
|
|
247
|
-
})()`;
|
|
248
|
-
const script = [
|
|
249
|
-
'import json, time',
|
|
250
|
-
'from urllib.parse import urlsplit',
|
|
251
|
-
`url = ${JSON.stringify(`https://chatgpt.com${pathname}`)}`,
|
|
252
|
-
`marker = ${JSON.stringify(marker)}`,
|
|
253
|
-
'goto_url(url)',
|
|
254
|
-
'wait_for_load()',
|
|
255
|
-
'if urlsplit(page_info().get("url", "")).path.rstrip("/") != urlsplit(url).path.rstrip("/"): raise RuntimeError("review thread redirect mismatch")',
|
|
256
|
-
'turns = []',
|
|
257
|
-
'for attempt in range(30):',
|
|
258
|
-
` turns = js(${JSON.stringify(inspectJs)}) or []`,
|
|
259
|
-
' if turns: break',
|
|
260
|
-
' time.sleep(0.5)',
|
|
261
|
-
'if not turns: raise RuntimeError("review thread transcript unavailable; refusing ambiguous resubmission")',
|
|
262
|
-
'matches = [turn for turn in turns if marker in turn.get("text", "")]',
|
|
263
|
-
'if len(matches) > 1: raise RuntimeError("duplicate review delivery detected; manual reconciliation required")',
|
|
264
|
-
'print(json.dumps({"found": bool(matches), "user_message_id": matches[0].get("id") if matches else None}), flush=True)',
|
|
265
|
-
].join('\n');
|
|
266
|
-
const envelope = parseWorkspaceJson(await browserWorkspaceCommand(cli, ['session', 'exec', sessionId], script, signal));
|
|
267
|
-
if (envelope.ok !== true || typeof envelope.stdout !== 'string')
|
|
268
|
-
throw new Error('review thread inspection returned no executable result');
|
|
269
|
-
const observation = parseWorkspaceJson(envelope.stdout);
|
|
270
|
-
if (typeof observation.found !== 'boolean')
|
|
271
|
-
throw new Error('review thread inspection returned no authoritative result');
|
|
272
|
-
if (!observation.found)
|
|
273
|
-
return null;
|
|
274
|
-
return { status: 'submitted', mode: 'new-turn', thread_id: delivery.threadId, project_id: delivery.projectId,
|
|
275
|
-
verified_by: 'existing-user-turn', user_message_id: typeof observation.user_message_id === 'string' ? observation.user_message_id : null };
|
|
276
|
-
}
|
|
277
|
-
async function runBrowserWorker(cli, configFile, signal, action = 'temporary-submit', delivery) {
|
|
231
|
+
async function runBrowserWorker(cli, configFile, signal, action = 'temporary-submit') {
|
|
278
232
|
const session = parseWorkspaceJson(await browserWorkspaceCommand(cli, ['session', 'start', '--workspace', CHATGPT_BROWSER_WORKSPACE], undefined, signal));
|
|
279
233
|
const sessionId = session.session_id;
|
|
280
234
|
if (typeof sessionId !== 'string' || !sessionId)
|
|
281
235
|
throw new Error('browser-workspace session start returned no session_id');
|
|
282
236
|
try {
|
|
283
|
-
if (delivery) {
|
|
284
|
-
const existing = await findExistingReviewTurn(cli, sessionId, delivery, signal);
|
|
285
|
-
if (existing)
|
|
286
|
-
return JSON.stringify(existing);
|
|
287
|
-
}
|
|
288
237
|
return await browserWorkspaceCommand(cli, ['platform', 'run', 'chatgpt', action, '--session-id', sessionId, '--config', configFile], undefined, signal);
|
|
289
238
|
}
|
|
290
239
|
finally {
|
|
@@ -310,7 +259,7 @@ function browserWorkerRunner() {
|
|
|
310
259
|
if (signal.aborted)
|
|
311
260
|
throw new Error('browser-worker execution aborted');
|
|
312
261
|
const action = delivery ? 'new-turn' : 'temporary-submit';
|
|
313
|
-
const output = await runBrowserWorker(cli, configFile, signal, action
|
|
262
|
+
const output = await runBrowserWorker(cli, configFile, signal, action);
|
|
314
263
|
return JSON.stringify(parseDriverJson(output));
|
|
315
264
|
}
|
|
316
265
|
finally {
|
package/dist/cli-help.js
CHANGED
|
@@ -58,7 +58,7 @@ Required: --repo OWNER/REPO --pr NUMBER --id JOB_ID.
|
|
|
58
58
|
Optional: --thread-id THREAD_ID persists a reviewer override; omitted uses Job reviewer/creator thread or legacy route. Rejects pending reviews and wakes managed reconciliation.`,
|
|
59
59
|
'job create': `Create a SQLite-authoritative Job (objective-only).
|
|
60
60
|
|
|
61
|
-
Required: --repo OWNER/REPO and a standalone Job objective. Job ID defaults to an auto-incrementing
|
|
61
|
+
Required: --repo OWNER/REPO and a standalone Job objective. Job ID defaults to an auto-incrementing store-global number; --id is optional for compatibility. Existing PR adoption is an explicit separate command: job adopt.
|
|
62
62
|
Common: --id JOB_ID (optional), --title TEXT, --objective-file PATH|- (preferred), --objective MARKDOWN (short/simple only), --project-path RELATIVE_PATH, --priority P0|P1|P2|P3, --mode fixed|autonomous, --initial-plan, --no-review, --tag JSON, --reviewer-thread-id ID, --creator-thread-id ID, --implementation-thread-id ID. --objective and --objective-file are mutually exclusive.
|
|
63
63
|
Job creation always enables auto-merge. By default it is create-only; --initial-plan validates the first real Planner round before atomically creating the durable Job and Plan, then publishes the normal wake so Relay can continue without a second orchestrator command. Re-running create against an already healthy Job is idempotent and does not mutate its metadata.
|
|
64
64
|
|
|
@@ -14,14 +14,17 @@ export function isGeneratedEntityId(id, repository, kind) {
|
|
|
14
14
|
const legacyPrefix = generatedEntityPrefix(repository, kind);
|
|
15
15
|
return id.startsWith(legacyPrefix) && /^[0-9]+$/.test(id.slice(legacyPrefix.length));
|
|
16
16
|
}
|
|
17
|
+
if (/^[1-9][0-9]*$/.test(id))
|
|
18
|
+
return true;
|
|
17
19
|
const prefix = generatedEntityPrefix(repository, kind);
|
|
18
20
|
return id.startsWith(prefix) && /^[0-9]+$/.test(id.slice(prefix.length));
|
|
19
21
|
}
|
|
20
|
-
/**
|
|
22
|
+
/** Job and Task IDs each use their own store-global sequence. SQLite serializes allocation; legacy identifiers remain readable. */
|
|
21
23
|
export function allocateEntityId(directory, repository, kind, requestKey, occupied = new Set()) {
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
24
|
+
generatedEntityPrefix(repository, kind); // validate repository even though its name no longer appears in the ID
|
|
25
|
+
const prefix = '';
|
|
26
|
+
const scope = '*';
|
|
27
|
+
const scopedRequestKey = requestKey ? `${repository}:${requestKey}` : undefined;
|
|
25
28
|
if (requestKey !== undefined && (!requestKey || requestKey.length > 512))
|
|
26
29
|
throw new Error('Invalid idempotency key');
|
|
27
30
|
mkdirSync(directory, { recursive: true });
|
|
@@ -39,6 +42,7 @@ export function allocateEntityId(directory, repository, kind, requestKey, occupi
|
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
const old = db.prepare('SELECT value FROM sequences WHERE repository=? AND kind=?').get(scope, kind);
|
|
45
|
+
// Existing generated numeric IDs across all repositories are reserved by the caller.
|
|
42
46
|
let next = (old?.value ?? 0) + 1;
|
|
43
47
|
while (occupied.has(prefix + next))
|
|
44
48
|
next++;
|
package/package.json
CHANGED
|
@@ -24,6 +24,14 @@ The initial decomposition belongs to that durable Planner round. The orchestrato
|
|
|
24
24
|
|
|
25
25
|
## P0 / self job workflow
|
|
26
26
|
|
|
27
|
+
For a self Job owned by the current XChat thread, resolve the current thread ID
|
|
28
|
+
before creating the Job. If it cannot be retrieved reliably, ask the user for
|
|
29
|
+
the thread ID before proceeding. Set `creatorThreadId`,
|
|
30
|
+
`implementationThreadId`, and `reviewerThreadId` to that same thread ID at
|
|
31
|
+
creation, using the corresponding `--creator-thread-id`,
|
|
32
|
+
`--implementation-thread-id`, and `--reviewer-thread-id` options. Do not infer
|
|
33
|
+
the thread ID from an unrelated worker, execution, or previous conversation.
|
|
34
|
+
|
|
27
35
|
P0/self work is ordinary durable Relay work. The orchestrator may choose normal
|
|
28
36
|
Task requirements, but every newly created work Task is executable by a
|
|
29
37
|
worker/model route selected by Execution Router. Relay has no privileged
|