agents-relay 2.0.164 → 2.0.166
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/planner.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseTestInjection } from './e2e-injection.js';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
6
6
|
import { storySizeTimeoutMs, taskOverallTimeoutMs } from './types.js';
|
|
7
|
-
import {
|
|
7
|
+
import { unresolvedTask } from './workflow/task-resolution.js';
|
|
8
8
|
import { executionContextPrompt, BUNDLED_AGENTS_ROOT } from './execution-context.js';
|
|
9
9
|
import { resolveAgentMarkdown } from './agent-content-resolver.js';
|
|
10
10
|
import { parseAgentGraph, validateAgentSelection } from './agent-graph.js';
|
|
@@ -116,7 +116,6 @@ export function materializePlannerChild(job, planner, spec, executionContext) {
|
|
|
116
116
|
const target = resolution.targetTaskId ? job.tasks.find(task => task.id === resolution.targetTaskId) : undefined;
|
|
117
117
|
if (!target || !unresolvedTask(target))
|
|
118
118
|
throw new Error(`Planner resolution for ${spec.id} must target one unresolved durable Task`);
|
|
119
|
-
validateResolutionProposal(job, target, resolution);
|
|
120
119
|
}
|
|
121
120
|
const reviewerThread = spec.agentName === 'reviewer' ? prepareReviewerThreadDelivery(job, { id: spec.id, agentName: spec.agentName, reviewHeadSha }) : undefined, implementationThread = (spec.agentName === 'implementation' || spec.agentName == null) && job.implementationThreadId ? job.implementationThreadId : undefined, threadOwned = Boolean(reviewerThread || implementationThread);
|
|
122
121
|
return createTaskEntity(job, { jobId: job.id, id: spec.id, title: spec.agentName === 'reviewer' ? 'Review' : spec.title, kind: 'work', priority: spec.priority ?? job.priority, storySize: spec.storySize ?? 'M', projectName: spec.projectName, agentName: spec.agentName, agentGraph: spec.agentGraph, review: spec.review, parentTaskId: objectiveRoot?.id ?? spec.parentTaskId ?? null, dependencies: spec.dependencies ?? [], capabilities: spec.capabilities ?? [], workerRequirements: threadOwned ? chatgptThreadRequirements(spec.workerRequirements) : spec.workerRequirements, testInjection: spec.testInjection, adapter: threadOwned ? 'chatgpt' : (spec.adapter ?? 'codex'), chatgptThreadDelivery: reviewerThread, input: spec.input, output: spec.output, continuation: spec.continuation ?? null, continuationDeliveredAt: null, attempt: 0, maxAttempts: spec.maxAttempts ?? 3, leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null, error: null, timeoutMs: spec.timeoutMs ?? taskOverallTimeoutMs(spec.storySize ?? 'M'), createdAt: now, updatedAt: now, planningDecisionId: decisionId, planningSource: source, planningRound: round, reviewHeadSha, resolution: resolution ? { ...resolution, state: 'proposed', owner: 'planner', updatedAt: now } : undefined });
|
|
@@ -542,8 +541,10 @@ export class AgentObjectivePlanner {
|
|
|
542
541
|
throw new Error('Planner execution is missing its durable Plan and execution identity');
|
|
543
542
|
const response = await runAgent(prompt);
|
|
544
543
|
const expected = `PLAN_APPLIED:${context.planExecution.planId}:${context.planExecution.executionId}`;
|
|
545
|
-
if (response !== expected)
|
|
546
|
-
|
|
544
|
+
if (response !== expected) {
|
|
545
|
+
const kind = response.startsWith('PLAN_APPLIED:') ? 'mismatched' : 'missing';
|
|
546
|
+
throw new Error(`Planner returned a ${kind} PLAN_APPLIED receipt; must return only the matching PLAN_APPLIED receipt. Expected ${expected}, received ${response.slice(0, 200) || '(empty final message)'}. Inspect durable Plan state before retrying; only PLAN_APPLY_VALIDATION_FAILED authorizes same-execution candidate correction.`);
|
|
547
|
+
}
|
|
547
548
|
return { objective_status: 'satisfied', assessment: 'Plan committed through the authorized CLI action.', next_tasks: [], unblocks: [], cliApplied: true };
|
|
548
549
|
}
|
|
549
550
|
}
|
package/dist/pool.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ReconcileCoordinator } from './reconcile-coordinator.js';
|
|
|
6
6
|
import { errorMessage, observability, withObservabilityCorrelation } from './observability.js';
|
|
7
7
|
import { jobHasActiveExecution } from './job-execution-slot.js';
|
|
8
8
|
import { unblockNeedsNewPlan } from './unblock.js';
|
|
9
|
+
import { pendingTroubleshooterRetries } from './troubleshooter.js';
|
|
9
10
|
const RANK = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
10
11
|
export function compareJobPriority(a, b) { return RANK[a.priority ?? 'P2'] - RANK[b.priority ?? 'P2'] || a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id); }
|
|
11
12
|
export function runnableManagedJobs(items) { return items.filter(item => item.job.state === 'ACTIVE' || item.job.projectionRecovery?.status === 'repairing').map(item => item.job).sort(compareJobPriority); }
|
|
@@ -20,6 +21,8 @@ export function needsLifecycleReconciliation(job, now = Date.now()) {
|
|
|
20
21
|
return true;
|
|
21
22
|
if (job.executionMode === 'autonomous' && unblockNeedsNewPlan(job))
|
|
22
23
|
return true;
|
|
24
|
+
if (pendingTroubleshooterRetries(job).length > 0)
|
|
25
|
+
return true;
|
|
23
26
|
if (job.tasks.some(task => (task.state === 'QUEUED' && task.executionId === null)
|
|
24
27
|
|| (task.state === 'RUNNING' && task.adapter === 'orchestrator' && task.executionId === null)
|
|
25
28
|
|| (task.state === 'DRAFT' && task.dependencies.length > 0)))
|
package/dist/reconciler.js
CHANGED
|
@@ -26,7 +26,7 @@ import { executeManualPlan } from './manual-plan.js';
|
|
|
26
26
|
import { executionEventSource, withExecutionBoundary } from './reconciler-runtime.js';
|
|
27
27
|
export { executionEventSource } from './reconciler-runtime.js';
|
|
28
28
|
import { CallbackWorkflowEffectHandler } from './workflow/effects.js';
|
|
29
|
-
import { exhaustedTroubleshooterRecoveries, pendingTroubleshooterTasks, pendingTroubleshooterReopens, refreshTroubleshooterInput } from './troubleshooter.js';
|
|
29
|
+
import { exhaustedTroubleshooterRecoveries, pendingTroubleshooterTasks, pendingTroubleshooterReopens, pendingTroubleshooterRetries, refreshTroubleshooterInput } from './troubleshooter.js';
|
|
30
30
|
import { fallbackFailedTroubleshooters, escalateExhaustedTroubleshooters } from './troubleshooter-fallback.js';
|
|
31
31
|
import { taskUsesCanonicalProjectRun } from './workspace.js';
|
|
32
32
|
import { cleanupMergedJobWorktree } from './job-worktree-cleanup.js';
|
|
@@ -142,12 +142,7 @@ export class Reconciler extends ReconcilerReviewBase {
|
|
|
142
142
|
}
|
|
143
143
|
{
|
|
144
144
|
const recoveryJob = await this.store.load(jobId);
|
|
145
|
-
for (const recovery of recoveryJob
|
|
146
|
-
if (recovery.agentName !== 'troubleshooter' || recovery.state !== 'FAILED' || recovery.attempt >= recovery.maxAttempts)
|
|
147
|
-
continue;
|
|
148
|
-
const original = recoveryJob.tasks.find(task => task.id === recovery.parentTaskId);
|
|
149
|
-
if (!original || !['FAILED', 'BLOCKED'].includes(original.state))
|
|
150
|
-
continue;
|
|
145
|
+
for (const { original, recovery } of pendingTroubleshooterRetries(recoveryJob)) {
|
|
151
146
|
await applyTaskRetryAction(recovery);
|
|
152
147
|
recovery.routing = undefined;
|
|
153
148
|
recovery.workerSelection = undefined;
|
package/dist/troubleshooter.js
CHANGED
|
@@ -65,6 +65,14 @@ export function pendingTroubleshooterReopens(job) {
|
|
|
65
65
|
}
|
|
66
66
|
return pending;
|
|
67
67
|
}
|
|
68
|
+
export function pendingTroubleshooterRetries(job) {
|
|
69
|
+
return job.tasks.flatMap(recovery => {
|
|
70
|
+
if (recovery.agentName !== 'troubleshooter' || recovery.state !== 'FAILED' || recovery.attempt >= recovery.maxAttempts)
|
|
71
|
+
return [];
|
|
72
|
+
const original = job.tasks.find(task => task.id === recovery.parentTaskId);
|
|
73
|
+
return original && ['FAILED', 'BLOCKED'].includes(original.state) ? [{ original, recovery }] : [];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
68
76
|
export function refreshTroubleshooterInput(job, original, recovery) {
|
|
69
77
|
recovery.input = failureInput(job, original, recovery.id);
|
|
70
78
|
recovery.recoveryForAttempt = original.attempt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-relay",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.166",
|
|
4
4
|
"description": "Durable async agent jobs coordinated through GitHub pull requests",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"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",
|
|
21
21
|
"test:full": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 test/*.test.js && npm run test:verification:l3",
|
|
22
22
|
"test:verification:l1": "npm run build && node --test test/verification/harness.test.mjs test/verification/contract.test.mjs test/verification/graph-inventory.test.mjs test/verification/dashboard-drilldown.test.mjs test/verification/evidence-matrix.test.mjs test/verification/task-resolution-classification.test.mjs test/verification/planner-contract.test.mjs test/verification/force-restate-validation.test.mjs test/planner-child-recovery.test.js",
|
|
23
|
-
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs",
|
|
23
|
+
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs test/verification/l3-daemon-startup-recovery.test.mjs",
|
|
24
24
|
"test:verification": "npm run test:verification:l1 && npm run test:verification:l2 && npm run test:verification:l3 && npm run test:verification:cross-a && node --test test/workflow-production-outbox.test.js && npm run test:verification:matrix",
|
|
25
25
|
"e2e": "node e2e/runner/run.mjs",
|
|
26
26
|
"test:verification:cross-a": "npm run build && RELAY_A_DIST=$PWD/dist node --test test/verification/action-ledger-cross-branch.test.mjs test/verification/outbox-cross-a.test.mjs",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"test:verification:l2": "npm run build && node --test test/verification/l2/*.test.mjs",
|
|
29
29
|
"test:verification:l2:live": "node test/verification/l2/live-provider.mjs",
|
|
30
30
|
"test:verification:l2:live-codex": "npm run build && node test/verification/l2/live-codex-agents.mjs",
|
|
31
|
+
"test:verification:l2:live-planner": "npm run build && RELAY_L2_LIVE=1 node --test test/verification/l2/live-planner-convergence.test.mjs",
|
|
31
32
|
"test:l2:agents": "npm run build && node test/l2-agent-prompts/evaluate.mjs"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
@@ -4,7 +4,11 @@ You are the default objective planner for Agents Relay autonomous jobs.
|
|
|
4
4
|
|
|
5
5
|
Before producing tasks, read and follow `../contracts/task-job-contract.md`.
|
|
6
6
|
|
|
7
|
-
You
|
|
7
|
+
You own end-to-end objective convergence. Relay invokes you with the durable Job, existing Tasks, previous planner rounds, and resolved project context. Investigate the actual objective and execution evidence, decide whether it is satisfied, and otherwise produce safe executable progress or an evidence-backed block. A Task Tree is a means to that outcome, not the outcome itself.
|
|
8
|
+
|
|
9
|
+
Every proposal MUST be committed with the provided `agents-relay plan apply` CLI command. Write the proposed JSON to a temporary file and run that command with the exact Plan and execution IDs from `plan_execution` / `apply_cli`. If it exits unsuccessfully with a JSON error whose code is `PLAN_APPLY_VALIDATION_FAILED`, read its `phase` and every `issues` entry, correct the candidate, and retry within this SAME Planner execution using the same Plan and execution IDs, up to three corrections. This is an actionable candidate rejection: it does not persist the Plan or any Task/history mutation, and the CLI output is the evidence for the correction. Keep the original evidence and intent; change only what the reported issues require. For `phase: "fencing"`, refresh the durable context and stop if the active execution no longer owns the Plan; never bypass the fence. For any other CLI failure, diagnose it from its actual output and do not claim validation feedback that was not returned.
|
|
10
|
+
|
|
11
|
+
A CLI rejection (`PLAN_APPLY_VALIDATION_FAILED`) is distinct from a missing apply receipt. Only successful CLI output followed by the exact final message `PLAN_APPLIED:<plan-id>:<execution-id>` proves this execution committed. A missing, mismatched, or decorated receipt is not a validation failure and must never be fabricated or retried as though the apply were rejected. If the Plan may have committed but its receipt is unavailable, stop without claiming success; Relay/runtime must resolve the durable Plan state. The CLI prints the persisted Plan JSON on success; that output alone is not your final receipt. Do not edit SQLite or other durable storage directly. Do not treat a JSON/text proposal as a plan submission and do not return the proposal as your final answer. Never invent the receipt. If no live execution identity or CLI action is available, fail rather than returning a proposal.
|
|
8
12
|
|
|
9
13
|
Relay persists each planning round as a first-class Plan PR comment/marker before generated Task comments. The Plan carries its trigger/context (including unblock/change facts when applicable), assessment, and generated Task IDs. Legacy `kind=planner` Task records may still exist as execution history, but they are not the durable semantic container for the planning decision. Returned work Tasks are not automatically children of the Plan; use `parentTaskId` only when the business Task graph needs hierarchy.
|
|
10
14
|
|
|
@@ -101,9 +105,9 @@ Do not create separate Tasks merely for "analyze", "format", "call tool", "check
|
|
|
101
105
|
|
|
102
106
|
### Forced Restate audit and validation
|
|
103
107
|
|
|
104
|
-
When `force_restate` is true, use this single forced Planner opportunity to audit the full authoritative semantic Task tree, every historical Task and Resolution classification, all dependency edges, and review target/head evidence. Inspect every ID in `restate_task_ids` against authoritative Task records and evidence. Do not run `task classify` or `task cancel` as a preliminary mutation.
|
|
108
|
+
When `force_restate` is true, use this single forced Planner opportunity to audit the full authoritative semantic Task tree, every historical Task and Resolution classification, all dependency edges, and review target/head evidence. Inspect every ID in `restate_task_ids` against authoritative Task records and evidence before constructing the candidate. Do not run `task classify` or `task cancel` as a preliminary mutation. Include exactly one `history_cleanup` entry for every ID in `restate_task_ids` in the first complete candidate you submit: `{ "taskId", "outcome": "superseded"|"invalid"|"cancelled_review"|"unresolved", "evidence", "reason", "targetTaskId?" }`. Never defer a disposition or omit one while submitting partial work. `plan apply` validates the complete proposal against the current SQLite snapshot and atomically applies dispositions with the Plan and Tasks only after all validation succeeds. A failed validation leaves all SQLite state unchanged; use its exact phase and issues to correct the candidate and retry with the same Plan and execution IDs.
|
|
105
109
|
|
|
106
|
-
If evidence is absent or execution outcome is unknown,
|
|
110
|
+
If evidence is absent or execution outcome is unknown, disposition that historical Task as `unresolved`; never classify, cancel, or imply success without proof. A classification target must be an existing SUCCEEDED Task. An unresolved failure may still need safe recovery: when the objective requires it, create executable recovery work with a valid `resolution` targeting the original unresolved Task, preserving the original failure and uncertainty. If no recovery can be justified from evidence, create this Plan's own evidence-backed blocked Troubleshooter with explicit unblock conditions. Do not use unresolved history as a reason to silently abandon the objective. Every Plan apply must converge to supported worker-routed runnable work, fully accountable dependency waiting whose closure reaches runnable work, this Plan's own evidence-backed blocked Troubleshooter with explicit unblock conditions, or an already COMPLETED Job. Never submit an empty plan to hide unresolved work or unavailable routes. When implementation is complete at a newer PR head, let canonical final-review reconciliation create the full root review; plan new implementation/review work only when that graph cannot advance.
|
|
107
111
|
|
|
108
112
|
- Prefer independently verifiable Tasks with stable IDs, but do not split work solely to make Tasks smaller.
|
|
109
113
|
- When a Task depends on mutable repository or product state, distinguish a frozen evidence target from the current execution head. A SHA may freeze that Task's own review/source evidence, but do not copy an upstream SHA into a dependent release, deploy, publication, or other follow-on Task unless the objective explicitly requires that same frozen target. After dependencies complete, downstream Tasks must resolve and verify the current durable Job/PR head before acting.
|