agents-relay 2.0.66 → 2.0.68
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 +24 -19
- package/dist/adapters.js +1 -1
- package/dist/cli-blocker.js +3 -5
- package/dist/cli-help.js +12 -10
- package/dist/cli-job-create.js +69 -0
- package/dist/cli-local-merge-config.js +9 -0
- package/dist/cli-support.js +25 -8
- package/dist/cli.js +66 -80
- package/dist/dashboard-html.js +1 -1
- package/dist/dashboard.js +1 -1
- package/dist/github-auth.js +4 -3
- package/dist/github-webhook.js +13 -5
- package/dist/local-authority-store.js +279 -0
- package/dist/local-git-merge.js +113 -0
- package/dist/local-job-path.js +7 -0
- package/dist/local-job-registry.js +23 -0
- package/dist/local-managed-store.js +21 -0
- package/dist/local-merge-delivery.js +52 -0
- package/dist/local-merge-lock.js +103 -0
- package/dist/no-github.js +11 -0
- package/dist/planned-review-head.js +7 -8
- package/dist/pool.js +72 -135
- package/dist/projected-store.js +20 -3
- package/dist/projection-outbox.js +3 -1
- package/dist/publication-store.js +37 -0
- package/dist/reconciler-review.js +25 -11
- package/dist/reconciler-runtime.js +15 -5
- package/dist/reconciler.js +33 -60
- package/dist/relay-store.js +108 -6
- package/dist/relayd.js +119 -124
- package/dist/runtime-status.js +8 -7
- package/dist/scheduled-jobs.js +43 -36
- package/dist/service-external-actions.js +27 -0
- package/dist/service-publication.js +29 -0
- package/dist/store-github.js +17 -6
- package/dist/task-update.js +1 -1
- package/dist/workflow/contracts.js +10 -1
- package/dist/workflow/definitions-v2.js +2 -0
- package/dist/workflow/job-reconcile-v2.js +9 -60
- package/dist/workflow/runtime.js +18 -3
- package/package.json +5 -3
- package/workflow/job-lifecycle.mmd +19 -60
- package/workflow/review-lifecycle.mmd +2 -8
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Agents Relay
|
|
2
2
|
|
|
3
|
-
Agents Relay is a
|
|
3
|
+
Agents Relay is a TypeScript runtime for durable asynchronous agent Jobs driven locally by SQLite and canonical workflow Graphs. The EventBus wakes reconciliation; an EventBus message or GitHub PR comment is never the lifecycle authority. Job, Plan, Task, Review, Execution, Lease and Recovery state survive daemon restarts in SQLite.
|
|
4
|
+
|
|
5
|
+
V2 uses a read-only local SQLite Job Registry for discovery. The immutable jobId selects the Job Store even when prNumber is zero; scheduled slot claims are atomic across processes. GitHub is **audit-only**: its PR/head/publish events are persisted for reporting, never consulted to start, stop, or complete local Workflows. Local Git delivery and its confirmed merge receipt—not a remote PR MERGED fact—gate Graph completion. Job creation and local Task operations require no GitHub authentication or PR.
|
|
4
6
|
|
|
5
|
-
V2 separates **operational execution state** from that collaboration record. A local RelayStore may use SQLite/WAL for runtime snapshots, event idempotency and a durable GitHub projection outbox. ProjectedDurableStore bootstraps from the existing GitHub representation, commits Job/Plan/Task runtime changes locally, and lets GitHubProjectionWriter project them asynchronously. GitHub therefore remains the human-readable durable Job/Plan/Task/Review surface without being required for every high-frequency runtime transition.
|
|
6
7
|
|
|
7
8
|
The canonical V2 architecture contract is
|
|
8
9
|
[DESIGN-PRINCIPLES.md](DESIGN-PRINCIPLES.md). README sections below describe
|
|
@@ -10,23 +11,27 @@ the runtime; they do not replace that contract.
|
|
|
10
11
|
|
|
11
12
|
## V2 architecture
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
\`\`\`mermaid
|
|
14
15
|
flowchart LR
|
|
15
|
-
Human[Human / orchestrator] -->
|
|
16
|
-
|
|
17
|
-
Worker[Workers / adapters] --> Events[
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Graphs -->
|
|
22
|
-
|
|
23
|
-
Store -->
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
Human[Human / orchestrator] --> CLI[CLI / Dashboard]
|
|
17
|
+
CLI --> Store[(SQLite / WAL<br/>Job Task Plan Review truth)]
|
|
18
|
+
Worker[Workers / adapters] --> Events[EventBus<br/>wake + observations]
|
|
19
|
+
Events --> Coordinator[ReconcileCoordinator]
|
|
20
|
+
Store --> Coordinator
|
|
21
|
+
Coordinator --> Graphs[Canonical Mermaid Graph Engine]
|
|
22
|
+
Graphs --> Effects[Stateless actions / effects]
|
|
23
|
+
Effects --> Store
|
|
24
|
+
Store --> Views[Dashboard / Scheduler / Recovery]
|
|
25
|
+
Effects --> Outbox[Durable external-action outbox]
|
|
26
|
+
Outbox --> GH[GitHub Commit / PR / Review / Merge API]
|
|
27
|
+
GH --> Facts[Confirmed external facts]
|
|
28
|
+
Facts --> Store
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
SQLite holds the sole lifecycle authority; the Graph Engine selects every transition.
|
|
32
|
+
GitHub operations are explicitly requested and confirmed by adapters, never used to
|
|
33
|
+
discover or reconstruct Job/Task state. EventBus delivery wakes reconciliation;
|
|
34
|
+
it cannot replace the durable SQLite record.
|
|
30
35
|
|
|
31
36
|
### Workflow graph navigation
|
|
32
37
|
|
|
@@ -53,7 +58,7 @@ These paths always represent the current canonical graphs. Historical graph defi
|
|
|
53
58
|
|
|
54
59
|
## Quick start
|
|
55
60
|
|
|
56
|
-
For
|
|
61
|
+
For new work, create a local Job through Agents Relay. The CLI can bind a known PR number without GitHub access; creating a new PR head uses the external GitHub adapter. Tasks are validated against the SQLite Job, not GitHub markers.
|
|
57
62
|
|
|
58
63
|
~~~sh
|
|
59
64
|
cat > job.md <<'EOF'
|
package/dist/adapters.js
CHANGED
|
@@ -146,7 +146,7 @@ export function parseDriverJson(output) {
|
|
|
146
146
|
throw new Error('browser-worker driver returned no submitted receipt');
|
|
147
147
|
}
|
|
148
148
|
function browserWorkspaceCli(override = process.env.AGENTS_RELAY_BROWSER_WORKSPACE_COMMAND) {
|
|
149
|
-
return override ? { command: override, prefix: [] } : { command: '
|
|
149
|
+
return override ? { command: override, prefix: [] } : { command: 'uvx', prefix: ['browser-workspace'] };
|
|
150
150
|
}
|
|
151
151
|
function browserWorkspaceCommand(cli, args, stdin, signal) {
|
|
152
152
|
return new Promise((resolve, reject) => {
|
package/dist/cli-blocker.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { githubAuthContext } from './github-auth.js';
|
|
1
|
+
import { openLocalManagedStore } from './local-managed-store.js';
|
|
3
2
|
import { registerBlockerDependency } from './blockers.js';
|
|
4
3
|
import { arg, publishDependentUnblocked, repository, withEventBus } from './cli-support.js';
|
|
5
4
|
import { usage } from './cli-help.js';
|
|
@@ -11,8 +10,7 @@ export async function runBlockerCommand(action, args) {
|
|
|
11
10
|
const pr = Number(arg(args, '--pr'));
|
|
12
11
|
if (!id || !Number.isInteger(pr) || pr <= 0)
|
|
13
12
|
throw new Error('blocker commands require --repo, --pr, and --id for the blocker Job');
|
|
14
|
-
const
|
|
15
|
-
const blockerStore = new GitHubStore(auth.client, repo, pr, auth.trustedAuthors);
|
|
13
|
+
const blockerStore = openLocalManagedStore(repo, pr).store;
|
|
16
14
|
if (action === 'show') {
|
|
17
15
|
console.log(JSON.stringify(await blockerStore.load(id), null, 2));
|
|
18
16
|
return;
|
|
@@ -36,7 +34,7 @@ export async function runBlockerCommand(action, args) {
|
|
|
36
34
|
const relation = await registerBlockerDependency({
|
|
37
35
|
blockerStore,
|
|
38
36
|
blockerJobId: id,
|
|
39
|
-
dependentStore:
|
|
37
|
+
dependentStore: openLocalManagedStore(dependentRepo, dependentPr).store,
|
|
40
38
|
dependentJobId: dependentJob,
|
|
41
39
|
dependentTaskId: dependentTask,
|
|
42
40
|
resolutionMode: modeValue,
|
package/dist/cli-help.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export const usage = 'agents-relay <job|task|review|plan|blocker|projection|escalation|context|init|record|agent-register|agent-discover|status|reconcile|serve> [options]';
|
|
2
2
|
const help = {
|
|
3
|
-
root: `Agents Relay coordinates durable asynchronous agent jobs through
|
|
3
|
+
root: `Agents Relay coordinates durable asynchronous agent jobs through local SQLite and workflow graphs.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
npx agents-relay <command> [options]
|
|
6
|
+
npx agents-relay <command> [options] [--no-github]
|
|
7
|
+
|
|
8
|
+
--no-github: SQLite/Graph-only mode. GitHub auth/API, PR creation, merge and fetch are disabled. Local completion is Graph-owned.
|
|
7
9
|
|
|
8
10
|
Commands:
|
|
9
|
-
job create|update|show|list|merge|unblock|cancel Manage
|
|
11
|
+
job create|update|show|list|merge|unblock|cancel Manage SQLite-authoritative jobs with optional GitHub PRs
|
|
10
12
|
task create|update|show|list|complete|retry|unblock|cancel Manage durable tasks
|
|
11
13
|
review request Request graph-owned review (optional thread override)
|
|
12
14
|
plan Invoke the Planner for an existing managed Job
|
|
@@ -24,7 +26,7 @@ Commands:
|
|
|
24
26
|
|
|
25
27
|
Run 'npx agents-relay <command> --help' for command options and examples.
|
|
26
28
|
Use 'node dist/cli.js' only for explicit local development/demo mode.`,
|
|
27
|
-
job: `Manage
|
|
29
|
+
job: `Manage SQLite-authoritative durable jobs.
|
|
28
30
|
|
|
29
31
|
Usage:
|
|
30
32
|
npx agents-relay job <create|update|show|list|merge|unblock|cancel> [options]
|
|
@@ -34,7 +36,7 @@ Actions:
|
|
|
34
36
|
update Partially update mutable metadata on a healthy existing Job
|
|
35
37
|
show Show one durable Job
|
|
36
38
|
list List managed Jobs in a repository
|
|
37
|
-
merge
|
|
39
|
+
merge Queue a retryable external PR merge; Graph completes the Job only after confirmation
|
|
38
40
|
unblock Authoritatively invalidate current BLOCKED decisions and trigger replanning/resume
|
|
39
41
|
cancel Close the backing PR and cancel the Job
|
|
40
42
|
|
|
@@ -54,10 +56,10 @@ When provided, --thread-id persists a reviewer destination; otherwise the Job re
|
|
|
54
56
|
|
|
55
57
|
Required: --repo OWNER/REPO --pr NUMBER --id JOB_ID.
|
|
56
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.`,
|
|
57
|
-
'job create': `Create a
|
|
59
|
+
'job create': `Create a SQLite-authoritative Job.
|
|
58
60
|
|
|
59
61
|
Required: --repo OWNER/REPO, --id JOB_ID, and either --head BRANCH or --pr NUMBER.
|
|
60
|
-
With --head, reuse/create the PR for that head. With --pr, bind an
|
|
62
|
+
With --head, reuse/create the PR for that head. With --pr, bind an external PR reference locally without a GitHub query.
|
|
61
63
|
Common: --base BRANCH (default: main), --title TEXT, --objective-file PATH|- (preferred), --objective MARKDOWN (short/simple only), --project-path RELATIVE_PATH, --priority P0|P1|P2|P3, --mode fixed|autonomous, --no-review, --tag JSON, --reviewer-thread-id ID, --creator-thread-id ID. --objective and --objective-file are mutually exclusive. For a new --head Job, one is required and must contain enough standalone context to complete the Job without the originating chat/thread. Objective Markdown is also the default PR body; --body is an explicit override.
|
|
62
64
|
Job creation always enables auto-merge and never creates executable tasks. The orchestrator decides the initial task batch separately. Re-running create against an already healthy Job is idempotent and does not mutate its metadata.
|
|
63
65
|
|
|
@@ -66,8 +68,8 @@ Example:
|
|
|
66
68
|
'job update': `Partially update mutable metadata on a healthy existing Job.
|
|
67
69
|
|
|
68
70
|
Required: --repo OWNER/REPO, --pr NUMBER, and --id JOB_ID.
|
|
69
|
-
Mutable: --title, --objective-file PATH|- (preferred) or --objective MARKDOWN, --project-path RELATIVE_PATH, --priority, --mode fixed|autonomous, --schedule, --tag JSON, --auto-merge|--no-auto-merge, --review|--no-review, --reviewer-thread-id ID, --creator-thread-id ID, and continuation options.
|
|
70
|
-
Unspecified fields are preserved.
|
|
71
|
+
Mutable: --title, --objective-file PATH|- (preferred) or --objective MARKDOWN, --project-path RELATIVE_PATH, --priority, --mode fixed|autonomous, --schedule, --tag JSON, --auto-merge|--no-auto-merge, --review|--no-review, --reviewer-thread-id ID, --creator-thread-id ID, and continuation options. The objective updates local SQLite; PR body changes are independent external actions.
|
|
72
|
+
Unspecified fields are preserved. Local Job/PR identity mismatch is rejected.
|
|
71
73
|
|
|
72
74
|
Example:
|
|
73
75
|
npx agents-relay job update --repo OWNER/REPO --pr 12 --id job-1 --priority P0 --no-auto-merge`,
|
|
@@ -83,7 +85,7 @@ Required: --repo OWNER/REPO.
|
|
|
83
85
|
|
|
84
86
|
Example:
|
|
85
87
|
npx agents-relay job list --repo OWNER/REPO`,
|
|
86
|
-
'job merge': `
|
|
88
|
+
'job merge': `Request an idempotent external pull-request merge; return QUEUED until confirmed, when the Graph Engine completes the Job.
|
|
87
89
|
|
|
88
90
|
Required: --repo OWNER/REPO, --pr NUMBER, and --id JOB_ID. The current PR head SHA is pinned into the merge request.`,
|
|
89
91
|
'job unblock': `Authoritatively unblock the effective BLOCKED work Tasks in one Job.\n\nRequired: --repo OWNER/REPO --pr NUMBER --id JOB_ID --reason TEXT. Autonomous Jobs transition blocked work to historical UNBLOCKED state and wake Planner with unblock facts as Plan trigger/context; fixed Jobs record UNBLOCKED first and then resume according to fixed-job retry semantics.`,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { openLocalManagedStore } from './local-managed-store.js';
|
|
2
|
+
import { createJobEntity } from './workflow/entity-creation.js';
|
|
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';
|
|
7
|
+
/**
|
|
8
|
+
* Job lifecycle bootstrap: SQLite is authoritative. PR creation is an
|
|
9
|
+
* optional one-time external adapter; an explicit PR number is offline-safe.
|
|
10
|
+
*/
|
|
11
|
+
export async function ensureManagedGitHubJob(_client, repositoryName, _trustedAuthors, options) {
|
|
12
|
+
if (!options.id)
|
|
13
|
+
throw new Error('--id is required for managed local Jobs');
|
|
14
|
+
const number = options.prNumber ?? 0;
|
|
15
|
+
if (!Number.isInteger(number) || number < 0)
|
|
16
|
+
throw new Error('Invalid external PR binding number');
|
|
17
|
+
const pr = { number, title: options.title ?? options.id,
|
|
18
|
+
body: options.body ?? options.objective ?? '', state: 'OPEN', head: '', base: options.base ?? '' };
|
|
19
|
+
const directory = options.storeDirectory ?? join(homedir(), '.agents-relay', 'stores');
|
|
20
|
+
const alreadyKnown = (await discoverLocalRelayStoreSnapshots(directory)).flatMap(snapshot => snapshot.jobs.filter(job => job.id === options.id).map(job => ({ job, path: snapshot.path })));
|
|
21
|
+
if (alreadyKnown.length > 1)
|
|
22
|
+
throw new Error('Duplicate immutable Job ID across SQLite stores: ' + options.id);
|
|
23
|
+
if (alreadyKnown.length === 1) {
|
|
24
|
+
const existing = alreadyKnown[0].job;
|
|
25
|
+
if (existing.repository !== repositoryName || (options.prNumber !== undefined && existing.prNumber !== number))
|
|
26
|
+
throw new Error('Immutable Job ID is already bound to a different repository or external PR');
|
|
27
|
+
return { job: existing, pr: { ...pr, number: existing.prNumber } };
|
|
28
|
+
}
|
|
29
|
+
const local = openLocalManagedStore(repositoryName, number, {
|
|
30
|
+
directory, jobId: options.id, noGitHub: true,
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const existing = await local.relay.loadJob(options.id);
|
|
34
|
+
if (existing)
|
|
35
|
+
return { job: existing, pr };
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
const job = createJobEntity({
|
|
38
|
+
id: options.id,
|
|
39
|
+
title: options.title || pr.title || 'Agents Relay job',
|
|
40
|
+
objective: options.objective || pr.body || undefined,
|
|
41
|
+
projectPath: options.projectPath,
|
|
42
|
+
description: pr.body || undefined,
|
|
43
|
+
priority: options.priority ?? 'P2',
|
|
44
|
+
executionMode: options.executionMode ?? 'fixed',
|
|
45
|
+
tag: options.tag,
|
|
46
|
+
autoMerge: true,
|
|
47
|
+
...(options.localMergeSource && options.localMergeTarget ? { localMerge: { sourceRef: options.localMergeSource, targetRef: options.localMergeTarget, status: 'pending' } } : {}),
|
|
48
|
+
reviewEnabled: options.reviewEnabled ?? true,
|
|
49
|
+
...(options.reviewerThreadId
|
|
50
|
+
? { reviewerThreadId: normalizeThreadId(options.reviewerThreadId, 'reviewer thread ID') }
|
|
51
|
+
: {}),
|
|
52
|
+
...(options.creatorThreadId
|
|
53
|
+
? { creatorThreadId: normalizeThreadId(options.creatorThreadId, 'creator thread ID') }
|
|
54
|
+
: {}),
|
|
55
|
+
prNumber: pr.number,
|
|
56
|
+
repository: repositoryName,
|
|
57
|
+
continuation: options.continuation ?? null,
|
|
58
|
+
createdAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
tasks: [],
|
|
61
|
+
...(options.schedule ? { schedule: options.schedule } : {}),
|
|
62
|
+
}, { activate: Boolean(options.schedule) });
|
|
63
|
+
await local.store.saveJob(job);
|
|
64
|
+
return { job: await local.store.load(options.id), pr };
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
local.relay.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function configureLocalGitMerge(job, source, target, localOnly) {
|
|
2
|
+
// The GitHub integration is observational; local merge applies in either mode.
|
|
3
|
+
void localOnly;
|
|
4
|
+
if (!source || !target)
|
|
5
|
+
throw new Error('Supply both --local-merge-source and --local-merge-target');
|
|
6
|
+
if (!['ACTIVE', 'DRAFT'].includes(job.state))
|
|
7
|
+
throw new Error('Cannot reconfigure merge of a terminal Job');
|
|
8
|
+
job.localMerge = { sourceRef: source, targetRef: target, status: 'pending' };
|
|
9
|
+
}
|
package/dist/cli-support.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { InMemoryStore, GitHubStore } from './store.js';
|
|
3
3
|
import { githubAuthContext } from './github-auth.js';
|
|
4
|
+
import { noGitHubMode } from './no-github.js';
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
import { createJobIdRegistryResolver } from './local-job-registry.js';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
4
9
|
import { NatsEventBus, eventFor } from './events.js';
|
|
5
10
|
import { parseJobSchedule } from './scheduler.js';
|
|
6
11
|
import { parseJobTagJson } from './task-tag.js';
|
|
@@ -51,20 +56,32 @@ export async function textOption(args, inlineFlag, fileFlag) {
|
|
|
51
56
|
export function integerArg(args, name, fallback) { if (!hasArg(args, name))
|
|
52
57
|
return fallback; const value = Number(arg(args, name)); if (!Number.isInteger(value) || value < 0)
|
|
53
58
|
throw new Error(`${name} must be a non-negative integer`); return value; }
|
|
54
|
-
export function repository(args) { const value = arg(args, '--repo'); if (!value)
|
|
59
|
+
export function repository(args) { const value = arg(args, '--repo'); if (!value && noGitHubMode(args))
|
|
60
|
+
return 'local/' + basename(process.cwd()); if (!value)
|
|
55
61
|
throw new Error('--repo is required for GitHub-backed operation'); return value; }
|
|
56
62
|
export function pullRequest(args) { const value = Number(arg(args, '--pr')); if (!Number.isInteger(value) || value <= 0)
|
|
57
63
|
throw new Error('--pr is required for GitHub-backed operation'); return value; }
|
|
58
64
|
async function localStore(file) { const parsed = JSON.parse(await readFile(file, 'utf8')); const store = new InMemoryStore(parsed); return { store, job: await store.load(parsed.id) }; }
|
|
59
65
|
export async function githubStore(args) { const auth = await githubAuthContext(args); return new GitHubStore(auth.client, repository(args), pullRequest(args), auth.trustedAuthors); }
|
|
60
|
-
export async function storeFor(args) {
|
|
61
|
-
const
|
|
66
|
+
export async function storeFor(args) {
|
|
67
|
+
const file = arg(args, '--file');
|
|
68
|
+
if (file) {
|
|
69
|
+
const loaded = await localStore(file);
|
|
70
|
+
return { ...loaded, localFile: file };
|
|
71
|
+
}
|
|
72
|
+
const repo = noGitHubMode(args) ? repository(args) : arg(args, '--repo');
|
|
62
73
|
const jobId = arg(args, '--id');
|
|
63
|
-
if (!jobId)
|
|
64
|
-
throw new Error('--
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
if (!repo || !jobId)
|
|
75
|
+
throw new Error('Local Job requires --repo and --id (or explicit --file)');
|
|
76
|
+
const directory = arg(args, '--store-dir') || join(homedir(), '.agents-relay', 'stores');
|
|
77
|
+
const binding = await createJobIdRegistryResolver(directory, new Map())(jobId);
|
|
78
|
+
if (!binding || binding.job.repository !== repo)
|
|
79
|
+
throw new Error('Immutable local Job not found for repository: ' + jobId);
|
|
80
|
+
const suppliedPr = arg(args, '--pr');
|
|
81
|
+
if (suppliedPr && binding.job.prNumber !== Number(suppliedPr))
|
|
82
|
+
throw new Error('Local Job external PR binding mismatch');
|
|
83
|
+
return { store: binding.store, job: await binding.store.load(jobId), localFile: null };
|
|
84
|
+
}
|
|
68
85
|
export async function saveLocal(file, job) { if (file)
|
|
69
86
|
await writeFile(file, JSON.stringify(job, null, 2)); }
|
|
70
87
|
export async function relayControlJob(args, jobId) {
|
package/dist/cli.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { openLocalManagedStore } from './local-managed-store.js';
|
|
5
|
+
import { configureLocalGitMerge } from './cli-local-merge-config.js';
|
|
6
|
+
import { DisabledGitHubClient, noGitHubMode } from './no-github.js';
|
|
7
|
+
import { discoverLocalRelayStoreSnapshots } from './relay-store.js';
|
|
2
8
|
import { realpathSync } from 'node:fs';
|
|
3
9
|
import { once } from 'node:events';
|
|
4
10
|
import { fileURLToPath } from 'node:url';
|
|
5
11
|
import { randomUUID } from 'node:crypto';
|
|
6
|
-
import { dashboardManagedJobs, InMemoryStore
|
|
7
|
-
import { githubAuthContext } from './github-auth.js';
|
|
12
|
+
import { dashboardManagedJobs, InMemoryStore } from './store.js';
|
|
8
13
|
import { taskOverallTimeoutMs } from './types.js';
|
|
9
14
|
import { Reconciler } from './reconciler.js';
|
|
10
15
|
import { dashboardJobSetState, dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
11
16
|
export { dashboardJobSetState, dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
12
17
|
import { CodexAdapter, ChatGptAdapter } from './adapters.js';
|
|
13
18
|
import { CodexThreadContinuation, CommandContinuation, WebhookContinuation } from './continuation.js';
|
|
14
|
-
import { eventFor } from './events.js';
|
|
15
19
|
import { serveDashboard } from './dashboard.js';
|
|
16
20
|
import { codexAndZaiUsageRegistry } from './usage.js';
|
|
17
21
|
import { decisionMode, executionDecisionEngine } from './decision.js';
|
|
18
22
|
import { discoverAgents } from './registry.js';
|
|
19
23
|
import { AgentObjectivePlanner, CommandObjectivePlanner } from './planner.js';
|
|
20
|
-
import { transitionJobEvent } from './scheduler.js';
|
|
21
24
|
import { applyOrchestratorStartAction, applyOrchestratorCompleteAction, applyTaskHoldAction, applyTaskQueueAction, dispatchTaskAction } from './task-actions.js';
|
|
22
25
|
import { applyJobActivateAction, applyJobCancelAction } from './job-actions.js';
|
|
23
26
|
import { resolveExecutionContext } from './execution-context.js';
|
|
27
|
+
import { startServiceExternalActionFlusher } from './service-external-actions.js';
|
|
28
|
+
import { handleServicePublication } from './service-publication.js';
|
|
24
29
|
import { defaultWorkspaceRoot, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
|
|
25
30
|
import { runBlockerCommand } from './cli-blocker.js';
|
|
26
31
|
import { runEscalationCommand } from './cli-escalation.js';
|
|
@@ -35,46 +40,10 @@ export { helpText } from './cli-help.js';
|
|
|
35
40
|
import { runReviewCommand } from './cli-review.js';
|
|
36
41
|
import { normalizeThreadId, runtimeReviewer } from './reviewer-thread.js';
|
|
37
42
|
export { runtimeReviewer } from './reviewer-thread.js';
|
|
38
|
-
import { SERVICE_DEFAULTS, ServiceEventBus, arg, hasArg, textOption, integerArg, repository, storeFor, saveLocal,
|
|
43
|
+
import { SERVICE_DEFAULTS, ServiceEventBus, arg, hasArg, textOption, integerArg, repository, pullRequest, storeFor, saveLocal, eventBus, publishWake, priority, storySize, executionMode, chatGptTabClosePolicy, requestedExecutionMode, continuation, scheduleFromArgs, jobTagFromArgs, requestManagedReconciliation, relayControlJob, relayTaskCommand, withEventBus } from './cli-support.js';
|
|
39
44
|
export { SERVICE_DEFAULTS, eventBus, executionMode, chatGptTabClosePolicy } from './cli-support.js';
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new Error('--id is required for managed GitHub jobs');
|
|
43
|
-
const pr = await resolvePullRequest(client, repositoryName, { prNumber: options.prNumber, head: options.head, base: options.base, title: options.title, body: options.body });
|
|
44
|
-
const store = new GitHubStore(client, repositoryName, pr.number, trustedAuthors);
|
|
45
|
-
try {
|
|
46
|
-
const existing = await store.load(options.id);
|
|
47
|
-
return { job: existing, pr };
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if (!(error instanceof Error) || !error.message.startsWith('JOB_MARKER_MISSING:'))
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
const now = new Date().toISOString();
|
|
54
|
-
const job = createJobEntity({
|
|
55
|
-
id: options.id,
|
|
56
|
-
title: options.title || pr.title || 'Agents Relay job',
|
|
57
|
-
objective: options.objective || pr.body || undefined,
|
|
58
|
-
projectPath: options.projectPath,
|
|
59
|
-
description: pr.body || undefined,
|
|
60
|
-
priority: options.priority ?? 'P2',
|
|
61
|
-
executionMode: options.executionMode ?? 'fixed',
|
|
62
|
-
tag: options.tag,
|
|
63
|
-
autoMerge: true,
|
|
64
|
-
reviewEnabled: options.reviewEnabled ?? true,
|
|
65
|
-
...(options.reviewerThreadId ? { reviewerThreadId: normalizeThreadId(options.reviewerThreadId, 'reviewer thread ID') } : {}),
|
|
66
|
-
...(options.creatorThreadId ? { creatorThreadId: normalizeThreadId(options.creatorThreadId, 'creator thread ID') } : {}),
|
|
67
|
-
prNumber: pr.number,
|
|
68
|
-
repository: repositoryName,
|
|
69
|
-
continuation: options.continuation ?? null,
|
|
70
|
-
createdAt: now,
|
|
71
|
-
updatedAt: now,
|
|
72
|
-
tasks: [],
|
|
73
|
-
...(options.schedule ? { schedule: options.schedule } : {}),
|
|
74
|
-
}, { activate: Boolean(options.schedule) });
|
|
75
|
-
await store.saveJob(job);
|
|
76
|
-
return { job: await store.load(options.id), pr };
|
|
77
|
-
}
|
|
45
|
+
import { ensureManagedGitHubJob } from './cli-job-create.js';
|
|
46
|
+
export { ensureManagedGitHubJob } from './cli-job-create.js';
|
|
78
47
|
async function workerTaskFromArgs(job, args, now) {
|
|
79
48
|
const model = arg(args, '--model');
|
|
80
49
|
const adapterValue = arg(args, '--adapter');
|
|
@@ -166,7 +135,7 @@ async function runManagedTaskCommand(action, args) {
|
|
|
166
135
|
async function runTaskCommand(action, args) {
|
|
167
136
|
if (!['create', 'update', 'show', 'list', 'start', 'complete', 'retry', 'unblock', 'cancel'].includes(action))
|
|
168
137
|
throw new Error(usage);
|
|
169
|
-
const managed = Boolean(arg(args, '--repo') && arg(args, '--pr') && !arg(args, '--file'));
|
|
138
|
+
const managed = !noGitHubMode(args) && Boolean(arg(args, '--repo') && arg(args, '--pr') && !arg(args, '--file'));
|
|
170
139
|
if (managed && ['create', 'start', 'complete', 'retry', 'cancel'].includes(action)) {
|
|
171
140
|
await runManagedTaskCommand(action, args);
|
|
172
141
|
return;
|
|
@@ -365,27 +334,35 @@ async function runJobCommand(action, args) {
|
|
|
365
334
|
if (invalid)
|
|
366
335
|
throw new Error(`job create does not accept task execution option ${invalid}; create the job first, then let the orchestrator create the initial plan/task batch`);
|
|
367
336
|
createObjective = await textOption(args, '--objective', '--objective-file');
|
|
368
|
-
if (!arg(args, '--pr') && !createObjective.trim())
|
|
337
|
+
if ((!arg(args, '--pr') || noGitHubMode(args)) && !createObjective.trim())
|
|
369
338
|
throw new Error('job create with --head requires --objective-file PATH|- or --objective MARKDOWN containing the complete standalone Job context');
|
|
370
339
|
}
|
|
371
|
-
const auth = await githubAuthContext(args);
|
|
372
340
|
if (action === 'list') {
|
|
373
|
-
|
|
341
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(join(homedir(), '.agents-relay', 'stores'));
|
|
342
|
+
const overviews = snapshots.flatMap(snapshot => snapshot.jobs.filter(job => job.repository === repo)
|
|
343
|
+
.map(job => ({
|
|
344
|
+
job, githubState: snapshot.facts?.state ?? 'UNKNOWN',
|
|
345
|
+
draft: false, mergedAt: null, closedAt: null, publication: snapshot.publications[job.id]?.latest ?? null, publicationFailure: snapshot.publications[job.id]?.failure ?? null,
|
|
346
|
+
})));
|
|
347
|
+
console.log(JSON.stringify(dashboardManagedJobs(overviews), null, 2));
|
|
374
348
|
return;
|
|
375
349
|
}
|
|
376
350
|
const id = arg(args, '--id');
|
|
377
351
|
if (!id)
|
|
378
|
-
throw new Error('--id is required for managed
|
|
352
|
+
throw new Error('--id is required for managed local Jobs');
|
|
379
353
|
const prValue = arg(args, '--pr');
|
|
380
|
-
const prNumber = prValue ? Number(prValue) :
|
|
354
|
+
const prNumber = prValue ? Number(prValue) : 0;
|
|
381
355
|
if (prValue && (!Number.isInteger(prNumber) || (prNumber ?? 0) <= 0))
|
|
382
356
|
throw new Error('--pr must be a positive integer');
|
|
383
357
|
if (action === 'create') {
|
|
384
|
-
if (!prNumber && !arg(args, '--head'))
|
|
385
|
-
throw new Error('job create requires either --head or --pr');
|
|
386
358
|
const objective = createObjective ?? '';
|
|
359
|
+
if (hasArg(args, '--local-merge-source') !== hasArg(args, '--local-merge-target'))
|
|
360
|
+
throw new Error('Supply both --local-merge-source and --local-merge-target');
|
|
361
|
+
const auth = { client: new DisabledGitHubClient(), trustedAuthors: new Set() };
|
|
387
362
|
const result = await ensureManagedGitHubJob(auth.client, repo, auth.trustedAuthors, {
|
|
388
|
-
id,
|
|
363
|
+
id, noGitHub: noGitHubMode(args),
|
|
364
|
+
localMergeSource: arg(args, '--local-merge-source') || undefined,
|
|
365
|
+
localMergeTarget: arg(args, '--local-merge-target') || undefined,
|
|
389
366
|
title: arg(args, '--title') || undefined,
|
|
390
367
|
objective: objective || undefined,
|
|
391
368
|
projectPath: arg(args, '--project-path') || undefined,
|
|
@@ -405,10 +382,8 @@ async function runJobCommand(action, args) {
|
|
|
405
382
|
console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state }, null, 2));
|
|
406
383
|
return;
|
|
407
384
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const store = new GitHubStore(auth.client, repo, prNumber, auth.trustedAuthors);
|
|
411
|
-
const job = await store.load(id);
|
|
385
|
+
const loaded = await storeFor(args);
|
|
386
|
+
const { store, job } = loaded;
|
|
412
387
|
if (action === 'unblock') {
|
|
413
388
|
await runJobUnblockCommand(args, store, job);
|
|
414
389
|
return;
|
|
@@ -417,22 +392,14 @@ async function runJobCommand(action, args) {
|
|
|
417
392
|
console.log(JSON.stringify(job, null, 2));
|
|
418
393
|
return;
|
|
419
394
|
}
|
|
420
|
-
|
|
395
|
+
if (job.repository !== repo || (prValue && job.prNumber !== prNumber)) {
|
|
396
|
+
throw new Error('Local Job/PR binding mismatch');
|
|
397
|
+
}
|
|
421
398
|
if (action === 'merge') {
|
|
422
|
-
|
|
423
|
-
throw new Error(`Cannot merge cancelled job ${job.id}`);
|
|
424
|
-
const headSha = await store.headSha();
|
|
425
|
-
await store.mergePullRequest(headSha ?? undefined);
|
|
426
|
-
await transitionJobEvent(job, 'pr.merged');
|
|
427
|
-
await store.saveJob(job);
|
|
428
|
-
const resolved = await store.resolveBlockerDependents(id, 'merge');
|
|
429
|
-
await withEventBus(args, bus => publishDependentUnblocked(bus, resolved.unblocked, `Blocker Job ${id} resolved (merge)`));
|
|
430
|
-
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
431
|
-
return;
|
|
399
|
+
throw new Error('GitHub is audit-only. Configure local merge source/target and reconcile the local Job instead.');
|
|
432
400
|
}
|
|
433
401
|
if (action === 'cancel') {
|
|
434
402
|
await applyJobCancelAction(store, job);
|
|
435
|
-
await store.closePullRequest();
|
|
436
403
|
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
437
404
|
return;
|
|
438
405
|
}
|
|
@@ -446,6 +413,8 @@ async function runJobCommand(action, args) {
|
|
|
446
413
|
}
|
|
447
414
|
if (hasArg(args, '--project-path'))
|
|
448
415
|
job.projectPath = arg(args, '--project-path') || undefined;
|
|
416
|
+
if (hasArg(args, '--local-merge-source') || hasArg(args, '--local-merge-target'))
|
|
417
|
+
configureLocalGitMerge(job, arg(args, '--local-merge-source'), arg(args, '--local-merge-target'), noGitHubMode(args));
|
|
449
418
|
if (hasArg(args, '--priority'))
|
|
450
419
|
job.priority = priority(args, job.priority ?? 'P2');
|
|
451
420
|
if (hasArg(args, '--mode') || hasArg(args, '--execution-mode'))
|
|
@@ -481,7 +450,7 @@ async function runJobCommand(action, args) {
|
|
|
481
450
|
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
482
451
|
}
|
|
483
452
|
export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
|
|
484
|
-
export function runtime(store, args, bus, usageRegistry = codexAndZaiUsageRegistry()) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const modelRuntime = arg(args, '--codex', 'codex'); const policy = chatGptTabClosePolicy(args); const planner = runtimePlanner(args); return new Reconciler(store, { owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new CodexAdapter(modelRuntime), new ChatGptAdapter({ tabClosePolicy: policy })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, usageSnapshotProvider: () => usageRegistry.snapshot(), decisionEngine: executionDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(args), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: async (job) => resolveRepositoryProjectPath(await resolveManagedProjectPath(job.repository, arg(args, '--project-path'), arg(args, '--workspace-root') || defaultWorkspaceRoot()), job.projectPath) }); }
|
|
453
|
+
export function runtime(store, args, bus, usageRegistry = codexAndZaiUsageRegistry()) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const modelRuntime = arg(args, '--codex', 'codex'); const policy = chatGptTabClosePolicy(args); const planner = runtimePlanner(args); return new Reconciler(store, { noGitHub: noGitHubMode(args), owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new CodexAdapter(modelRuntime), new ChatGptAdapter({ tabClosePolicy: policy })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, usageSnapshotProvider: () => usageRegistry.snapshot(), decisionEngine: executionDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(args), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: async (job) => resolveRepositoryProjectPath(await resolveManagedProjectPath(noGitHubMode(args) ? undefined : job.repository, arg(args, '--project-path'), arg(args, '--workspace-root') || defaultWorkspaceRoot()), job.projectPath) }); }
|
|
485
454
|
export function dashboardJobAction(store, reconciler, action) {
|
|
486
455
|
return async (jobId) => {
|
|
487
456
|
const job = await store.load(jobId);
|
|
@@ -491,7 +460,6 @@ export function dashboardJobAction(store, reconciler, action) {
|
|
|
491
460
|
}
|
|
492
461
|
else if (action === 'cancel') {
|
|
493
462
|
await applyJobCancelAction(store, job);
|
|
494
|
-
await store.closePullRequest?.();
|
|
495
463
|
return await store.load(job.id);
|
|
496
464
|
}
|
|
497
465
|
return await reconciler.wake(job.id);
|
|
@@ -505,17 +473,33 @@ export async function createService(args) {
|
|
|
505
473
|
const usageWindowDays = Number.isInteger(requestedUsageDays) && requestedUsageDays > 0 ? requestedUsageDays : 1;
|
|
506
474
|
const usageRegistry = codexAndZaiUsageRegistry(undefined, usageWindowDays);
|
|
507
475
|
const reconciler = runtime(loaded.store, args, bus, usageRegistry);
|
|
508
|
-
const repo = arg(args, '--repo');
|
|
476
|
+
const repo = noGitHubMode(args) ? repository(args) : arg(args, '--repo');
|
|
509
477
|
let overviewProvider;
|
|
510
478
|
if (repo) {
|
|
511
|
-
|
|
512
|
-
|
|
479
|
+
overviewProvider = async () => {
|
|
480
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(join(homedir(), '.agents-relay', 'stores'));
|
|
481
|
+
return dashboardManagedJobs(snapshots.flatMap(snapshot => snapshot.jobs
|
|
482
|
+
.filter(job => job.repository === repo)
|
|
483
|
+
.map(job => ({
|
|
484
|
+
job, githubState: snapshot.facts?.state ?? 'UNKNOWN', draft: false,
|
|
485
|
+
mergedAt: null, closedAt: null, publication: snapshot.publications[job.id]?.latest ?? null, publicationFailure: snapshot.publications[job.id]?.failure ?? null,
|
|
486
|
+
}))));
|
|
487
|
+
};
|
|
513
488
|
}
|
|
514
489
|
const webhookSecret = arg(args, '--github-webhook-secret') || process.env.AGENTS_RELAY_GITHUB_WEBHOOK_SECRET || '';
|
|
515
|
-
const webhook =
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
490
|
+
const webhook = !noGitHubMode(args) && repo && webhookSecret ? {
|
|
491
|
+
secret: webhookSecret,
|
|
492
|
+
repositories: new Set([repo]),
|
|
493
|
+
pullRequest: loaded.job.prNumber,
|
|
494
|
+
wake: async (event) => {
|
|
495
|
+
if (event.pullRequestFacts && loaded.store.applyPullRequestFacts) {
|
|
496
|
+
await loaded.store.applyPullRequestFacts({
|
|
497
|
+
...event.pullRequestFacts, observedAt: event.pullRequestFacts.observedAt ?? new Date().toISOString(),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
await handleServicePublication(await loaded.store.load(loaded.job.id), event, bus);
|
|
501
|
+
},
|
|
502
|
+
} : undefined;
|
|
519
503
|
const dashboardServer = serveDashboard(loaded.store, Number(arg(args, '--port', String(SERVICE_DEFAULTS.port))), bus, Number(arg(args, '--refresh-ms', String(SERVICE_DEFAULTS.dashboardRefreshMs))), loaded.job.id, usageRegistry, overviewProvider, webhook, undefined, undefined, undefined, {
|
|
520
504
|
start: dashboardJobAction(loaded.store, reconciler, 'start'),
|
|
521
505
|
reconcile: dashboardJobAction(loaded.store, reconciler, 'reconcile'),
|
|
@@ -529,6 +513,7 @@ export async function createService(args) {
|
|
|
529
513
|
const stopWatcher = await reconciler.watch(loaded.job.id);
|
|
530
514
|
const watchdog = setInterval(() => void reconciler.reconcile(loaded.job.id), Number(arg(args, '--interval', String(SERVICE_DEFAULTS.watchdogMs))));
|
|
531
515
|
watchdog.unref();
|
|
516
|
+
const stopExternalActions = startServiceExternalActionFlusher(loaded.store, reconciler, loaded.job.id, Number(arg(args, '--projection-ms', String(SERVICE_DEFAULTS.projectionFlushMs))));
|
|
532
517
|
let stopping = null;
|
|
533
518
|
const service = {
|
|
534
519
|
store: loaded.store,
|
|
@@ -540,6 +525,7 @@ export async function createService(args) {
|
|
|
540
525
|
if (stopping)
|
|
541
526
|
return stopping;
|
|
542
527
|
clearInterval(watchdog);
|
|
528
|
+
stopExternalActions();
|
|
543
529
|
service.watchdogCleared = true;
|
|
544
530
|
const serverClosed = new Promise((resolve, reject) => { dashboardServer.close(error => error ? reject(error) : resolve()); });
|
|
545
531
|
dashboardServer.closeAllConnections();
|
|
@@ -576,7 +562,7 @@ export function isEntrypoint(moduleUrl) {
|
|
|
576
562
|
}
|
|
577
563
|
}
|
|
578
564
|
async function main() {
|
|
579
|
-
const [command, ...args] = process.argv.slice(2);
|
|
565
|
+
const [command, ...args] = process.argv.slice(2).filter(value => value !== '--no-github');
|
|
580
566
|
if (!command || command === '--help' || command === '-h') {
|
|
581
567
|
console.log(helpText());
|
|
582
568
|
return;
|
|
@@ -626,7 +612,7 @@ async function main() {
|
|
|
626
612
|
let store;
|
|
627
613
|
let file = null;
|
|
628
614
|
if (arg(args, '--repo') && arg(args, '--pr'))
|
|
629
|
-
store =
|
|
615
|
+
store = openLocalManagedStore(repository(args), pullRequest(args)).store;
|
|
630
616
|
else {
|
|
631
617
|
file = arg(args, '--file');
|
|
632
618
|
if (!file)
|
|
@@ -709,7 +695,7 @@ async function main() {
|
|
|
709
695
|
}
|
|
710
696
|
if (command === 'reconcile') {
|
|
711
697
|
await withEventBus(args, async (bus) => {
|
|
712
|
-
if (!localFile) {
|
|
698
|
+
if (!localFile && !noGitHubMode(args)) {
|
|
713
699
|
await requestManagedReconciliation(bus, job);
|
|
714
700
|
console.log(JSON.stringify(await store.load(job.id), null, 2));
|
|
715
701
|
return;
|
package/dist/dashboard-html.js
CHANGED
|
@@ -388,7 +388,7 @@ function jobChatRef(job){return repoName(job?.repository)+'/pr#'+value(job?.prNu
|
|
|
388
388
|
function chatShortcutUrl(prompt){return 'https://chatgpt.com/?prompt='+encodeURIComponent(prompt);}
|
|
389
389
|
const CHAT_SHORTCUT_TARGET='agents-relay-shortcut';
|
|
390
390
|
function chatShortcutField(job){const box=element('div','detail-group');box.append(element('p','detail-label',text('ChatGPT shortcut')));const select=document.createElement('select');select.className='job-filter job-chat-shortcut-select';select.setAttribute('aria-label','ChatGPT shortcut');select.append(new Option('',''));for(const item of parseChatShortcuts(chatShortcutText()))select.append(new Option(item.name,item.prompt));select.value='';select.addEventListener('change',()=>{if(!select.value)return;const prompt=select.value.replaceAll('%1',jobChatRef(job));window.open(chatShortcutUrl(prompt),CHAT_SHORTCUT_TARGET,'noopener');select.value='';});box.append(select);return box;}
|
|
391
|
-
function renderJobList(){jobList.replaceChildren();const visible=filteredJobs();if(!jobs.length){jobList.append(element('li','empty',text('No durable jobs discovered.')));return;}if(!visible.length){jobList.append(element('li','empty',text('No jobs match the selected repo.')));return;}for(const job of visible){const row=element('div','job-row'+(isCompleteUnmerged(job)?' complete-unmerged':''));row.setAttribute('aria-current',String(jobSelectionKey(job)===selectedJob));row.addEventListener('click',()=>selectJob(job));const content=element('div','job-content');const select=document.createElement('button');select.type='button';select.className='job-button';select.setAttribute('aria-current',String(jobSelectionKey(job)===selectedJob));select.title=job.title||job.id;select.addEventListener('click',event=>{event.stopPropagation();selectJob(job);});select.append(element('span','job-title',text(repoName(job.repository)+'/PR#'+value(job.prNumber)+': '+value(job.title||job.id))));const states=element('span','job-status-line');states.append(element('span','badge '+value(job.state),text(value(job.state))),element('span','badge '+value(job.runtimeStatus||'IDLE'),text(value(job.runtimeStatus||'IDLE'))));if(job.executionMode==='autonomous')states.append(element('span','badge flag-autonomous',text('AUTONOMOUS')));if(job.reviewEnabled!==false&&job.githubState!=='MERGED')states.append(element('span','badge flag-review',text('REVIEW REQUIRED')));if(job.
|
|
391
|
+
function renderJobList(){jobList.replaceChildren();const visible=filteredJobs();if(!jobs.length){jobList.append(element('li','empty',text('No durable jobs discovered.')));return;}if(!visible.length){jobList.append(element('li','empty',text('No jobs match the selected repo.')));return;}for(const job of visible){const row=element('div','job-row'+(isCompleteUnmerged(job)?' complete-unmerged':''));row.setAttribute('aria-current',String(jobSelectionKey(job)===selectedJob));row.addEventListener('click',()=>selectJob(job));const content=element('div','job-content');const select=document.createElement('button');select.type='button';select.className='job-button';select.setAttribute('aria-current',String(jobSelectionKey(job)===selectedJob));select.title=job.title||job.id;select.addEventListener('click',event=>{event.stopPropagation();selectJob(job);});select.append(element('span','job-title',text(repoName(job.repository)+'/PR#'+value(job.prNumber)+': '+value(job.title||job.id))));const states=element('span','job-status-line');states.append(element('span','badge '+value(job.state),text(value(job.state))),element('span','badge '+value(job.runtimeStatus||'IDLE'),text(value(job.runtimeStatus||'IDLE'))));if(job.executionMode==='autonomous')states.append(element('span','badge flag-autonomous',text('AUTONOMOUS')));if(job.reviewEnabled!==false&&job.githubState!=='MERGED')states.append(element('span','badge flag-review',text('REVIEW REQUIRED')));if(job.publication?.version)states.append(element('span','badge SUCCEEDED',text('v'+job.publication.version)));if(job.publicationFailure)states.append(element('span','badge FAILED',text('PUBLISH FAILED: '+value(job.publicationFailure.stage))));if(isCompleteUnmerged(job))states.append(element('span','badge MERGE_PENDING',text('MERGE PENDING')));const primary=element('div','job-primary');primary.append(states);const lastLine=element('div','job-last-line');lastLine.append(element('span','subtle',text(compactCounts(counts(job.tasks)))),element('span','subtle',text(relativeTime(job.updatedAt))));content.append(select,primary,lastLine);row.append(content);jobList.append(element('li','',row));}}
|
|
392
392
|
function taskTagDisplay(job,task){const display=task.tag&&job?.tag?.values?.[task.tag];return typeof display==='string'&&display?display:null;}
|
|
393
393
|
function taskSubtreeComplete(task,children){return ['SUCCEEDED','CANCELLED'].includes(task.state)&&(children.get(task.id)||[]).every(child=>taskSubtreeComplete(child,children));}
|
|
394
394
|
function taskNode(task,children,depth){const li=element('li');const nested=children.get(task.id)||[];const line=element('div','task-line');if(nested.length){const key=(selectedJob||'')+':'+task.id;const collapsed=collapsedTasks.has(key)||(!expandedTasks.has(key)&&taskSubtreeComplete(task,children));const fold=document.createElement('button');fold.type='button';fold.className='task-fold';fold.setAttribute('aria-expanded',String(!collapsed));fold.setAttribute('aria-label',(collapsed?'Expand ':'Collapse ')+task.id);fold.textContent=collapsed?'▸':'▾';line.append(fold);const ul=element('ul','tree');ul.hidden=collapsed;for(const child of nested)ul.append(taskNode(child,children,depth+1));fold.addEventListener('click',()=>{const next=!ul.hidden;ul.hidden=next;fold.setAttribute('aria-expanded',String(!next));fold.setAttribute('aria-label',(next?'Expand ':'Collapse ')+task.id);fold.textContent=next?'▸':'▾';if(next){collapsedTasks.add(key);expandedTasks.delete(key);}else{collapsedTasks.delete(key);expandedTasks.add(key);}});li.append(line,ul);}else li.append(line);const row=document.createElement('button');row.type='button';row.className='task-row';row.setAttribute('aria-current',String(task.id===selectedTaskId));row.title=task.id;row.addEventListener('click',()=>{const deselect=selectedTaskId===task.id;selectedTaskId=deselect?null:task.id;eventTaskId=deselect?null:task.id;rememberSelection();clearEvents();renderAll();void loadEventHistory();});const id=element('span','task-id',text(task.id));id.style.paddingLeft=Math.min(depth,4)*6+'px';row.append(id,element('span','badge '+value(task.state),text(task.state)));const tag=taskTagDisplay(selected(),task);if(tag){const badge=element('span','task-tag',text(tag));badge.title=(selected()?.tag?.description||'Task tag')+': '+task.tag;row.append(badge);}line.append(row);return li;}
|