agents-relay 2.0.69 → 2.0.71
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/cli-job-create.js +2 -4
- package/dist/cli-support.js +2 -3
- package/dist/cli.js +2 -4
- package/dist/local-authority-store.js +3 -1
- package/dist/local-job-path.js +5 -0
- package/dist/local-managed-store.js +2 -4
- package/dist/relay-store.js +1 -1
- package/dist/relayd-dashboard-actions.js +8 -7
- package/dist/relayd.js +7 -7
- package/dist/service-publication.js +2 -4
- package/package.json +4 -4
package/dist/cli-job-create.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { openLocalManagedStore } from './local-managed-store.js';
|
|
2
2
|
import { createJobEntity } from './workflow/entity-creation.js';
|
|
3
3
|
import { normalizeThreadId } from './reviewer-thread.js';
|
|
4
|
-
import { discoverLocalRelayStoreSnapshots } from './relay-store.js';
|
|
5
|
-
import { homedir } from 'node:os';
|
|
6
|
-
import { join } from 'node:path';
|
|
4
|
+
import { discoverLocalRelayStoreSnapshots, relayStoreDirectory } from './relay-store.js';
|
|
7
5
|
/**
|
|
8
6
|
* Job lifecycle bootstrap: SQLite is authoritative. No GitHub request is
|
|
9
7
|
* required; an explicit PR number is optional external metadata.
|
|
@@ -16,7 +14,7 @@ export async function ensureManagedGitHubJob(_client, repositoryName, _trustedAu
|
|
|
16
14
|
throw new Error('Invalid external PR binding number');
|
|
17
15
|
const placeholder = { number: requestedNumber, title: options.title ?? options.id,
|
|
18
16
|
body: options.body ?? options.objective ?? '', state: 'OPEN', head: options.head ?? '', base: options.base ?? '' };
|
|
19
|
-
const directory = options.storeDirectory ??
|
|
17
|
+
const directory = options.storeDirectory ?? relayStoreDirectory();
|
|
20
18
|
const alreadyKnown = (await discoverLocalRelayStoreSnapshots(directory)).flatMap(snapshot => snapshot.jobs.filter(job => job.id === options.id).map(job => ({ job, path: snapshot.path })));
|
|
21
19
|
if (alreadyKnown.length > 1)
|
|
22
20
|
throw new Error('Duplicate immutable Job ID across SQLite stores: ' + options.id);
|
package/dist/cli-support.js
CHANGED
|
@@ -4,8 +4,7 @@ import { githubAuthContext } from './github-auth.js';
|
|
|
4
4
|
import { noGitHubMode } from './no-github.js';
|
|
5
5
|
import { basename } from 'node:path';
|
|
6
6
|
import { createJobIdRegistryResolver } from './local-job-registry.js';
|
|
7
|
-
import {
|
|
8
|
-
import { join } from 'node:path';
|
|
7
|
+
import { relayStoreDirectory } from './relay-store.js';
|
|
9
8
|
import { NatsEventBus, eventFor } from './events.js';
|
|
10
9
|
import { parseJobSchedule } from './scheduler.js';
|
|
11
10
|
import { parseJobTagJson } from './task-tag.js';
|
|
@@ -73,7 +72,7 @@ export async function storeFor(args) {
|
|
|
73
72
|
const jobId = arg(args, '--id');
|
|
74
73
|
if (!repo || !jobId)
|
|
75
74
|
throw new Error('Local Job requires --repo and --id (or explicit --file)');
|
|
76
|
-
const directory = arg(args, '--store-dir') ||
|
|
75
|
+
const directory = arg(args, '--store-dir') || relayStoreDirectory();
|
|
77
76
|
const binding = await createJobIdRegistryResolver(directory, new Map())(jobId);
|
|
78
77
|
if (!binding || binding.job.repository !== repo)
|
|
79
78
|
throw new Error('Immutable local Job not found for repository: ' + jobId);
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
2
|
import { configureLocalGitMerge } from './cli-local-merge-config.js';
|
|
5
3
|
import { noGitHubMode } from './no-github.js';
|
|
6
|
-
import { discoverLocalRelayStoreSnapshots } from './relay-store.js';
|
|
4
|
+
import { discoverLocalRelayStoreSnapshots, relayStoreDirectory } from './relay-store.js';
|
|
7
5
|
import { realpathSync } from 'node:fs';
|
|
8
6
|
import { once } from 'node:events';
|
|
9
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -489,7 +487,7 @@ export async function createService(args) {
|
|
|
489
487
|
let overviewProvider;
|
|
490
488
|
if (repo) {
|
|
491
489
|
overviewProvider = async () => {
|
|
492
|
-
const snapshots = await discoverLocalRelayStoreSnapshots(
|
|
490
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(relayStoreDirectory());
|
|
493
491
|
return dashboardManagedJobs(snapshots.flatMap(snapshot => snapshot.jobs
|
|
494
492
|
.filter(job => job.repository === repo)
|
|
495
493
|
.map(job => ({
|
|
@@ -133,6 +133,8 @@ export class LocalAuthorityDurableStore {
|
|
|
133
133
|
const children = mergeInboundJobSnapshot(current, incoming);
|
|
134
134
|
const preserveTerminal = ['COMPLETED', 'CANCELLED'].includes(current.state)
|
|
135
135
|
&& !['COMPLETED', 'CANCELLED'].includes(incoming.state);
|
|
136
|
+
// A stale snapshot must not undo an already committed Graph activation.
|
|
137
|
+
const preserveActivation = current.state === 'ACTIVE' && incoming.state === 'DRAFT';
|
|
136
138
|
const review = current.review && incoming.review
|
|
137
139
|
&& current.review.updatedAt > incoming.review.updatedAt
|
|
138
140
|
? current.review : incoming.review;
|
|
@@ -142,7 +144,7 @@ export class LocalAuthorityDurableStore {
|
|
|
142
144
|
const scalarSource = incoming.updatedAt > current.updatedAt ? incoming : current;
|
|
143
145
|
return {
|
|
144
146
|
...current, ...scalarSource,
|
|
145
|
-
state: preserveTerminal ? current.state : scalarSource.state,
|
|
147
|
+
state: preserveTerminal || preserveActivation ? current.state : scalarSource.state,
|
|
146
148
|
updatedAt: current.updatedAt > incoming.updatedAt ? current.updatedAt : incoming.updatedAt,
|
|
147
149
|
tasks: children.tasks,
|
|
148
150
|
...(children.plans ? { plans: children.plans } : {}),
|
package/dist/local-job-path.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
/** Primary local store key is immutable Job ID, independent of a PR number. */
|
|
3
5
|
export function relayJobStorePath(directory, jobId) {
|
|
4
6
|
if (!jobId.trim() || jobId.length > 1024)
|
|
5
7
|
throw new Error('Invalid immutable Job ID');
|
|
6
8
|
return directory + '/job-' + createHash('sha256').update(jobId).digest('hex') + '.db';
|
|
7
9
|
}
|
|
10
|
+
export function relayStoreDirectory(env = process.env) {
|
|
11
|
+
return env.AGENTS_RELAY_STORE_DIR || join(homedir(), '.agents-relay', 'stores');
|
|
12
|
+
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { GhApiClient } from './github-client.js';
|
|
4
2
|
import { DisabledGitHubClient, noGitHubMode } from './no-github.js';
|
|
5
3
|
import { GitHubStore } from './store.js';
|
|
6
4
|
import { LocalAuthorityDurableStore } from './local-authority-store.js';
|
|
7
|
-
import { relayStorePath, relayJobStorePath, SQLiteRelayStore } from './relay-store.js';
|
|
5
|
+
import { relayStoreDirectory, relayStorePath, relayJobStorePath, SQLiteRelayStore } from './relay-store.js';
|
|
8
6
|
/** Open the canonical V2 SQLite authority without making any GitHub request. */
|
|
9
7
|
export function openLocalManagedStore(repository, prNumber, options = {}) {
|
|
10
8
|
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || repository.includes('..') || !Number.isInteger(prNumber) || prNumber < 0 || (prNumber === 0 && !options.jobId)) {
|
|
11
9
|
throw new Error('Local managed store requires a repository and either a positive PR number or immutable jobId');
|
|
12
10
|
}
|
|
13
|
-
const directory = options.directory ??
|
|
11
|
+
const directory = options.directory ?? relayStoreDirectory();
|
|
14
12
|
const relay = new SQLiteRelayStore(prNumber === 0 ? relayJobStorePath(directory, options.jobId) : relayStorePath(directory, repository, prNumber));
|
|
15
13
|
const client = prNumber === 0 || options.noGitHub || noGitHubMode() ? new DisabledGitHubClient() : options.client ?? new GhApiClient();
|
|
16
14
|
const github = new GitHubStore(client, repository, prNumber, options.trustedAuthors ?? new Set());
|
package/dist/relay-store.js
CHANGED
|
@@ -367,7 +367,7 @@ export class SQLiteRelayStore {
|
|
|
367
367
|
export function relayStorePath(directory, repository, prNumber) {
|
|
368
368
|
return directory + '/' + `${repository.replace('/', '_')}-${prNumber}.db`;
|
|
369
369
|
}
|
|
370
|
-
export { relayJobStorePath } from './local-job-path.js';
|
|
370
|
+
export { relayJobStorePath, relayStoreDirectory } from './local-job-path.js';
|
|
371
371
|
/**
|
|
372
372
|
* SQLite is the only job registry. Inventory queries must be strictly read-only:
|
|
373
373
|
* opening a writable RelayStore here would reset in-flight projection claims
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
export function createDashboardActions(deps) {
|
|
3
|
-
const {
|
|
4
|
-
const
|
|
3
|
+
const { getOverviews, requireLocalJobBinding, pool, dashboardJobAction, makeReconciler, concurrency, dashboardJobSetState, refreshAll, applyJobActivateAction, applyOrchestratorStartAction, applyOrchestratorCompleteAction, publishOrchestratorTaskStarted, publishOrchestratorTaskCompletion, bus, dashboardTaskAction, dashboardTaskSetState } = deps;
|
|
4
|
+
const currentOverviews = () => getOverviews();
|
|
5
|
+
const dashboardOverview = (jobId, target) => currentOverviews().find((item) => item.job.id === jobId
|
|
5
6
|
&& (!target?.repository || item.job.repository === target.repository)
|
|
6
7
|
&& (!target?.prNumber || (item.job.externalPrNumber ?? item.job.prNumber) === target.prNumber));
|
|
7
8
|
const workspaceJobAction = (action) => async (jobId) => {
|
|
8
|
-
const overview =
|
|
9
|
+
const overview = currentOverviews().find((item) => item.job.id === jobId);
|
|
9
10
|
if (!overview)
|
|
10
11
|
throw new Error(`Managed job ${jobId} not found`);
|
|
11
12
|
const store = (await requireLocalJobBinding(jobId)).store;
|
|
@@ -25,7 +26,7 @@ export function createDashboardActions(deps) {
|
|
|
25
26
|
return result;
|
|
26
27
|
};
|
|
27
28
|
const workspaceTaskCreate = async (jobId, task) => {
|
|
28
|
-
const overview =
|
|
29
|
+
const overview = currentOverviews().find((item) => item.job.id === jobId);
|
|
29
30
|
if (!overview)
|
|
30
31
|
throw new Error(`Managed job ${jobId} not found`);
|
|
31
32
|
if (task.jobId !== jobId)
|
|
@@ -59,7 +60,7 @@ export function createDashboardActions(deps) {
|
|
|
59
60
|
return await store.load(jobId);
|
|
60
61
|
};
|
|
61
62
|
const workspaceTaskStart = async (jobId, taskId) => {
|
|
62
|
-
const overview =
|
|
63
|
+
const overview = currentOverviews().find((item) => item.job.id === jobId);
|
|
63
64
|
if (!overview)
|
|
64
65
|
throw new Error(`Managed job ${jobId} not found`);
|
|
65
66
|
const store = (await requireLocalJobBinding(jobId)).store;
|
|
@@ -74,7 +75,7 @@ export function createDashboardActions(deps) {
|
|
|
74
75
|
return await store.load(jobId);
|
|
75
76
|
};
|
|
76
77
|
const workspaceTaskComplete = async (jobId, taskId, summary) => {
|
|
77
|
-
const overview =
|
|
78
|
+
const overview = currentOverviews().find((item) => item.job.id === jobId);
|
|
78
79
|
if (!overview)
|
|
79
80
|
throw new Error(`Managed job ${jobId} not found`);
|
|
80
81
|
const store = (await requireLocalJobBinding(jobId)).store;
|
|
@@ -89,7 +90,7 @@ export function createDashboardActions(deps) {
|
|
|
89
90
|
return await store.load(jobId);
|
|
90
91
|
};
|
|
91
92
|
const workspaceTaskAction = (action) => async (jobId, taskId) => {
|
|
92
|
-
const overview =
|
|
93
|
+
const overview = currentOverviews().find((item) => item.job.id === jobId);
|
|
93
94
|
if (!overview)
|
|
94
95
|
throw new Error(`Managed job ${jobId} not found`);
|
|
95
96
|
const store = (await requireLocalJobBinding(jobId)).store;
|
package/dist/relayd.js
CHANGED
|
@@ -13,7 +13,7 @@ import { Reconciler } from './reconciler.js';
|
|
|
13
13
|
import { dashboardManagedJobs, GitHubStore, InMemoryStore, resolvePullRequest } from './store.js';
|
|
14
14
|
import { GitHubProjectionWriter } from './projected-store.js';
|
|
15
15
|
import { LocalAuthorityDurableStore } from './local-authority-store.js';
|
|
16
|
-
import { discoverLocalRelayStoreSnapshots, relayStorePath, SQLiteRelayStore } from './relay-store.js';
|
|
16
|
+
import { discoverLocalRelayStoreSnapshots, relayStoreDirectory, relayStorePath, SQLiteRelayStore } from './relay-store.js';
|
|
17
17
|
import { createJobIdRegistryResolver } from './local-job-registry.js';
|
|
18
18
|
import { projectAuthoritativeLocalMerge } from './local-merge-projection.js';
|
|
19
19
|
import { archiveDisplayPullRequest } from './github-pr-archive.js';
|
|
@@ -116,7 +116,7 @@ export async function runDaemon(argv) {
|
|
|
116
116
|
const key = `${repository}#${prNumber}`;
|
|
117
117
|
let relay = relayStores.get(key);
|
|
118
118
|
if (!relay) {
|
|
119
|
-
relay = new SQLiteRelayStore(relayStorePath(
|
|
119
|
+
relay = new SQLiteRelayStore(relayStorePath(relayStoreDirectory(), repository, prNumber));
|
|
120
120
|
relayStores.set(key, relay);
|
|
121
121
|
}
|
|
122
122
|
const github = new GitHubStore(client, repository, prNumber, trusted, seed);
|
|
@@ -127,7 +127,7 @@ export async function runDaemon(argv) {
|
|
|
127
127
|
return projected;
|
|
128
128
|
};
|
|
129
129
|
// SQLite inventory, not GitHub discovery, defines which repositories exist.
|
|
130
|
-
const existingInventory = await discoverLocalRelayStoreSnapshots(
|
|
130
|
+
const existingInventory = await discoverLocalRelayStoreSnapshots(relayStoreDirectory());
|
|
131
131
|
for (const snapshot of existingInventory)
|
|
132
132
|
for (const job of snapshot.jobs) {
|
|
133
133
|
if (!repositories.includes(job.repository))
|
|
@@ -136,7 +136,7 @@ export async function runDaemon(argv) {
|
|
|
136
136
|
if (repositories.length === 0)
|
|
137
137
|
repositories.push('local/' + basename(workspaceRoot));
|
|
138
138
|
const inventoryFromLocal = async () => {
|
|
139
|
-
const snapshots = await discoverLocalRelayStoreSnapshots(
|
|
139
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(relayStoreDirectory());
|
|
140
140
|
const known = new Set(repositories);
|
|
141
141
|
const entries = snapshots.flatMap(snapshot => snapshot.jobs.filter(job => known.has(job.repository)).map(job => ({
|
|
142
142
|
job, githubState: snapshot.facts?.state ?? 'UNKNOWN',
|
|
@@ -174,7 +174,7 @@ export async function runDaemon(argv) {
|
|
|
174
174
|
};
|
|
175
175
|
};
|
|
176
176
|
const refreshPullRequest = async (repository, prNumber, trigger, options) => (await refreshPullRequestSnapshot(repository, prNumber, trigger, options)).overviews;
|
|
177
|
-
const localJobBinding = createJobIdRegistryResolver(
|
|
177
|
+
const localJobBinding = createJobIdRegistryResolver(relayStoreDirectory(), relayStores, { client, trustedAuthors: trusted, noGitHub });
|
|
178
178
|
const requireLocalJobBinding = async (jobId) => {
|
|
179
179
|
const binding = await localJobBinding(jobId);
|
|
180
180
|
if (!binding)
|
|
@@ -214,7 +214,7 @@ export async function runDaemon(argv) {
|
|
|
214
214
|
return;
|
|
215
215
|
if (client.rateLimitStatus().limited)
|
|
216
216
|
return;
|
|
217
|
-
const snapshots = await discoverLocalRelayStoreSnapshots(
|
|
217
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(relayStoreDirectory());
|
|
218
218
|
const bindingCandidates = orderGitHubBindingCandidates(snapshots.flatMap(snapshot => snapshot.jobs.map(job => ({ snapshotPath: snapshot.path, job }))));
|
|
219
219
|
for (const candidate of bindingCandidates) {
|
|
220
220
|
const snapshot = { path: candidate.snapshotPath };
|
|
@@ -338,7 +338,7 @@ export async function runDaemon(argv) {
|
|
|
338
338
|
const initial = cachedOverviews;
|
|
339
339
|
const fallback = initial[0]?.job;
|
|
340
340
|
const dashboardStore = fallback ? (await requireLocalJobBinding(fallback.id)).store : new InMemoryStore();
|
|
341
|
-
const { dashboardOverview, workspaceJobAction, workspaceJobSetState, workspaceTaskCreate, workspaceTaskStart, workspaceTaskComplete, workspaceTaskAction, workspaceTaskSetState } = createDashboardActions({ cachedOverviews, requireLocalJobBinding, pool, dashboardJobAction, makeReconciler, concurrency, dashboardJobSetState, refreshAll, applyJobActivateAction, applyOrchestratorStartAction, applyOrchestratorCompleteAction, publishOrchestratorTaskStarted, publishOrchestratorTaskCompletion, bus, dashboardTaskAction, dashboardTaskSetState });
|
|
341
|
+
const { dashboardOverview, workspaceJobAction, workspaceJobSetState, workspaceTaskCreate, workspaceTaskStart, workspaceTaskComplete, workspaceTaskAction, workspaceTaskSetState } = createDashboardActions({ getOverviews: () => cachedOverviews, requireLocalJobBinding, pool, dashboardJobAction, makeReconciler, concurrency, dashboardJobSetState, refreshAll, applyJobActivateAction, applyOrchestratorStartAction, applyOrchestratorCompleteAction, publishOrchestratorTaskStarted, publishOrchestratorTaskCompletion, bus, dashboardTaskAction, dashboardTaskSetState });
|
|
342
342
|
const workspaceControl = createWorkspaceControl({ client, trusted, noGitHub, repositories, refreshAll, requireLocalJobBinding, makeReconciler, concurrency, pool, bus, isEffectivelyBlocked, unblockAutonomousTasks, unblockTask, publishTaskUnblocked, dispatchTaskAction, registerBlockerDependency, discoverAgents, ensureManagedGitHubJob });
|
|
343
343
|
const secretFile = value(argv, '--github-webhook-secret-file');
|
|
344
344
|
const secret = noGitHub ? '' : value(argv, '--github-webhook-secret') || process.env.AGENTS_RELAY_GITHUB_WEBHOOK_SECRET || (secretFile ? (await readFile(secretFile, 'utf8')).trim() : '');
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { eventFor } from './events.js';
|
|
4
|
-
import { SQLiteRelayStore, relayStorePath } from './relay-store.js';
|
|
2
|
+
import { SQLiteRelayStore, relayStoreDirectory, relayStorePath } from './relay-store.js';
|
|
5
3
|
/** Single-Job service uses the same durable publish facts as the repository daemon. */
|
|
6
|
-
export async function handleServicePublication(job, wake, bus, directory =
|
|
4
|
+
export async function handleServicePublication(job, wake, bus, directory = relayStoreDirectory()) {
|
|
7
5
|
if (!wake.publication && !wake.publishFailure)
|
|
8
6
|
return;
|
|
9
7
|
const sqlite = new SQLiteRelayStore(relayStorePath(directory, job.repository, job.prNumber));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-relay",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.71",
|
|
4
4
|
"description": "Durable async agent jobs coordinated through GitHub pull requests",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"test": "npm run test:full",
|
|
13
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
|
-
"test:publish": "npm run build && node --test --test-concurrency=1 --test-force-exit test/*.test.js",
|
|
16
|
-
"test:fast": "npm run build && node --test --test-concurrency=2 test/workflow-lifecycle.test.js test/workflow-v2-lifecycle-parity.test.js test/workflow-effects.test.js test/relay-store.test.js test/local-authority.test.js test/local-jobid-routing.test.js test/local-sqlite-jobid.test.js test/no-github.test.js test/scheduled-jobs.test.js test/reconcile-coordinator.test.js test/service-publication.test.js test/github-webhook.test.js",
|
|
17
|
-
"test:full": "npm run build && node --test --test-concurrency=1 test/*.test.js",
|
|
15
|
+
"test:publish": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 --test-force-exit test/*.test.js",
|
|
16
|
+
"test:fast": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=2 test/workflow-lifecycle.test.js test/workflow-v2-lifecycle-parity.test.js test/workflow-effects.test.js test/relay-store.test.js test/local-authority.test.js test/local-jobid-routing.test.js test/local-sqlite-jobid.test.js test/no-github.test.js test/scheduled-jobs.test.js test/reconcile-coordinator.test.js test/service-publication.test.js test/github-webhook.test.js",
|
|
17
|
+
"test:full": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 test/*.test.js",
|
|
18
18
|
"e2e": "node e2e/runner/run.mjs"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|