@renseiai/agentfactory 0.8.7 → 0.8.9
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/src/config/index.d.ts +1 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/repository-config.d.ts +37 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +47 -0
- package/dist/src/config/repository-config.test.js +140 -1
- package/dist/src/governor/decision-engine.d.ts +3 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +11 -0
- package/dist/src/governor/decision-engine.test.js +33 -0
- package/dist/src/governor/event-types.d.ts +18 -1
- package/dist/src/governor/event-types.d.ts.map +1 -1
- package/dist/src/governor/event-types.js +4 -0
- package/dist/src/governor/governor-types.d.ts +1 -1
- package/dist/src/governor/governor-types.d.ts.map +1 -1
- package/dist/src/governor/governor.d.ts +17 -1
- package/dist/src/governor/governor.d.ts.map +1 -1
- package/dist/src/governor/governor.js +112 -1
- package/dist/src/governor/governor.test.js +155 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.js +243 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
- package/dist/src/merge-queue/index.d.ts +18 -0
- package/dist/src/merge-queue/index.d.ts.map +1 -0
- package/dist/src/merge-queue/index.js +28 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
- package/dist/src/merge-queue/types.d.ts +48 -0
- package/dist/src/merge-queue/types.d.ts.map +1 -0
- package/dist/src/merge-queue/types.js +8 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.js +235 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
- package/dist/src/orchestrator/context-manager.d.ts +72 -0
- package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.js +120 -0
- package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
- package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.test.js +137 -0
- package/dist/src/orchestrator/index.d.ts +8 -2
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +8 -1
- package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.d.ts +12 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +282 -2
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +6 -0
- package/dist/src/orchestrator/parse-work-result.test.js +19 -0
- package/dist/src/orchestrator/state-recovery.d.ts +21 -2
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.js +54 -2
- package/dist/src/orchestrator/state-recovery.test.js +106 -2
- package/dist/src/orchestrator/state-types.d.ts +62 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/orchestrator/state-types.js +5 -1
- package/dist/src/orchestrator/summary-builder.d.ts +47 -0
- package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.js +240 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.test.js +236 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/orchestrator/work-types.d.ts +1 -1
- package/dist/src/orchestrator/work-types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +64 -1
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +132 -1
- package/dist/src/providers/index.test.js +340 -2
- package/dist/src/routing/index.d.ts +7 -0
- package/dist/src/routing/index.d.ts.map +1 -0
- package/dist/src/routing/index.js +6 -0
- package/dist/src/routing/observation-recorder.d.ts +19 -0
- package/dist/src/routing/observation-recorder.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.js +73 -0
- package/dist/src/routing/observation-recorder.test.d.ts +2 -0
- package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.test.js +322 -0
- package/dist/src/routing/observation-store.d.ts +40 -0
- package/dist/src/routing/observation-store.d.ts.map +1 -0
- package/dist/src/routing/observation-store.js +1 -0
- package/dist/src/routing/observation-store.test.d.ts +2 -0
- package/dist/src/routing/observation-store.test.d.ts.map +1 -0
- package/dist/src/routing/observation-store.test.js +138 -0
- package/dist/src/routing/posterior-store.d.ts +12 -0
- package/dist/src/routing/posterior-store.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.js +13 -0
- package/dist/src/routing/posterior-store.test.d.ts +2 -0
- package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.test.js +37 -0
- package/dist/src/routing/reward.d.ts +16 -0
- package/dist/src/routing/reward.d.ts.map +1 -0
- package/dist/src/routing/reward.js +29 -0
- package/dist/src/routing/reward.test.d.ts +2 -0
- package/dist/src/routing/reward.test.d.ts.map +1 -0
- package/dist/src/routing/reward.test.js +210 -0
- package/dist/src/routing/routing-engine.d.ts +20 -0
- package/dist/src/routing/routing-engine.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.js +113 -0
- package/dist/src/routing/routing-engine.test.d.ts +2 -0
- package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.test.js +310 -0
- package/dist/src/routing/types.d.ts +157 -0
- package/dist/src/routing/types.d.ts.map +1 -0
- package/dist/src/routing/types.js +68 -0
- package/dist/src/routing/types.test.d.ts +2 -0
- package/dist/src/routing/types.test.d.ts.map +1 -0
- package/dist/src/routing/types.test.js +184 -0
- package/dist/src/templates/registry.test.js +2 -2
- package/dist/src/templates/types.d.ts +5 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +3 -0
- package/dist/src/workflow/agent-cancellation.d.ts +37 -0
- package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.js +41 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.test.js +86 -0
- package/dist/src/workflow/branching-router.d.ts +38 -0
- package/dist/src/workflow/branching-router.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.js +52 -0
- package/dist/src/workflow/branching-router.test.d.ts +2 -0
- package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.test.js +209 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.js +46 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
- package/dist/src/workflow/duration.d.ts +28 -0
- package/dist/src/workflow/duration.d.ts.map +1 -0
- package/dist/src/workflow/duration.js +57 -0
- package/dist/src/workflow/duration.test.d.ts +2 -0
- package/dist/src/workflow/duration.test.d.ts.map +1 -0
- package/dist/src/workflow/duration.test.js +74 -0
- package/dist/src/workflow/expression/ast.d.ts +53 -0
- package/dist/src/workflow/expression/ast.d.ts.map +1 -0
- package/dist/src/workflow/expression/ast.js +8 -0
- package/dist/src/workflow/expression/context.d.ts +40 -0
- package/dist/src/workflow/expression/context.d.ts.map +1 -0
- package/dist/src/workflow/expression/context.js +37 -0
- package/dist/src/workflow/expression/evaluator.d.ts +28 -0
- package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.js +165 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.test.js +792 -0
- package/dist/src/workflow/expression/expression.test.d.ts +2 -0
- package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/expression.test.js +516 -0
- package/dist/src/workflow/expression/helpers.d.ts +21 -0
- package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
- package/dist/src/workflow/expression/helpers.js +56 -0
- package/dist/src/workflow/expression/index.d.ts +55 -0
- package/dist/src/workflow/expression/index.d.ts.map +1 -0
- package/dist/src/workflow/expression/index.js +71 -0
- package/dist/src/workflow/expression/lexer.d.ts +37 -0
- package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
- package/dist/src/workflow/expression/lexer.js +166 -0
- package/dist/src/workflow/expression/parser.d.ts +23 -0
- package/dist/src/workflow/expression/parser.d.ts.map +1 -0
- package/dist/src/workflow/expression/parser.js +181 -0
- package/dist/src/workflow/gate-state.d.ts +115 -0
- package/dist/src/workflow/gate-state.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.js +185 -0
- package/dist/src/workflow/gate-state.test.d.ts +2 -0
- package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.test.js +251 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.js +243 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
- package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
- package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.js +216 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.test.js +199 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.js +162 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
- package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
- package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.js +381 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.test.js +211 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.js +216 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
- package/dist/src/workflow/index.d.ts +31 -3
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +20 -1
- package/dist/src/workflow/parallelism-executor.d.ts +25 -0
- package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.js +53 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.test.js +191 -0
- package/dist/src/workflow/parallelism-types.d.ts +80 -0
- package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-types.js +8 -0
- package/dist/src/workflow/phase-context-injector.d.ts +29 -0
- package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.js +43 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.test.js +123 -0
- package/dist/src/workflow/phase-output-collector.d.ts +39 -0
- package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.js +141 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.test.js +179 -0
- package/dist/src/workflow/retry-resolver.d.ts +51 -0
- package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.js +70 -0
- package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
- package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.test.js +149 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
- package/dist/src/workflow/strategies/index.d.ts +4 -0
- package/dist/src/workflow/strategies/index.d.ts.map +1 -0
- package/dist/src/workflow/strategies/index.js +3 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.js +92 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
- package/dist/src/workflow/transition-engine.d.ts +3 -1
- package/dist/src/workflow/transition-engine.d.ts.map +1 -1
- package/dist/src/workflow/transition-engine.js +26 -7
- package/dist/src/workflow/transition-engine.test.js +215 -11
- package/dist/src/workflow/workflow-registry.d.ts +46 -1
- package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
- package/dist/src/workflow/workflow-registry.js +74 -0
- package/dist/src/workflow/workflow-registry.test.js +54 -0
- package/dist/src/workflow/workflow-types.d.ts +330 -12
- package/dist/src/workflow/workflow-types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.js +100 -5
- package/dist/src/workflow/workflow-types.test.js +293 -2
- package/package.json +2 -2
|
@@ -13,6 +13,7 @@ import { initializeAgentDir, writeState, updateState, writeTodos, createInitialS
|
|
|
13
13
|
import { createHeartbeatWriter, getHeartbeatIntervalFromEnv } from './heartbeat-writer.js';
|
|
14
14
|
import { createProgressLogger } from './progress-logger.js';
|
|
15
15
|
import { createSessionLogger } from './session-logger.js';
|
|
16
|
+
import { ContextManager } from './context-manager.js';
|
|
16
17
|
import { isSessionLoggingEnabled, getLogAnalysisConfig } from './log-config.js';
|
|
17
18
|
import { parseWorkResult } from './parse-work-result.js';
|
|
18
19
|
import { createActivityEmitter } from './activity-emitter.js';
|
|
@@ -21,6 +22,7 @@ import { createLogger } from '../logger.js';
|
|
|
21
22
|
import { TemplateRegistry, createToolPermissionAdapter } from '../templates/index.js';
|
|
22
23
|
import { loadRepositoryConfig, getProjectConfig, getProvidersConfig } from '../config/index.js';
|
|
23
24
|
import { ToolRegistry } from '../tools/index.js';
|
|
25
|
+
import { createMergeQueueAdapter } from '../merge-queue/index.js';
|
|
24
26
|
// Default inactivity timeout: 5 minutes
|
|
25
27
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 300000;
|
|
26
28
|
// Default max session timeout: unlimited (undefined)
|
|
@@ -368,6 +370,53 @@ function checkForIncompleteWork(worktreePath) {
|
|
|
368
370
|
};
|
|
369
371
|
}
|
|
370
372
|
}
|
|
373
|
+
function checkForPushedWorkWithoutPR(worktreePath) {
|
|
374
|
+
try {
|
|
375
|
+
const currentBranch = execSync('git branch --show-current', {
|
|
376
|
+
cwd: worktreePath,
|
|
377
|
+
encoding: 'utf-8',
|
|
378
|
+
timeout: 10000,
|
|
379
|
+
}).trim();
|
|
380
|
+
// If on main, no work to check
|
|
381
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
382
|
+
return { hasPushedWork: false };
|
|
383
|
+
}
|
|
384
|
+
// Count commits ahead of main
|
|
385
|
+
const aheadOutput = execSync(`git rev-list --count main..HEAD`, {
|
|
386
|
+
cwd: worktreePath,
|
|
387
|
+
encoding: 'utf-8',
|
|
388
|
+
timeout: 10000,
|
|
389
|
+
}).trim();
|
|
390
|
+
const aheadCount = parseInt(aheadOutput, 10);
|
|
391
|
+
if (aheadCount === 0) {
|
|
392
|
+
return { hasPushedWork: false };
|
|
393
|
+
}
|
|
394
|
+
// Branch has commits ahead of main — check if they've been pushed
|
|
395
|
+
try {
|
|
396
|
+
const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
|
|
397
|
+
cwd: worktreePath,
|
|
398
|
+
encoding: 'utf-8',
|
|
399
|
+
timeout: 10000,
|
|
400
|
+
}).trim();
|
|
401
|
+
if (remoteRef.length > 0) {
|
|
402
|
+
// Branch exists on remote with commits ahead of main — likely missing a PR
|
|
403
|
+
return {
|
|
404
|
+
hasPushedWork: true,
|
|
405
|
+
branch: currentBranch,
|
|
406
|
+
details: `Branch \`${currentBranch}\` has ${aheadCount} commit(s) ahead of main and has been pushed to the remote, but no PR was detected.`,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// ls-remote failed — can't confirm remote state
|
|
412
|
+
}
|
|
413
|
+
return { hasPushedWork: false };
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Git commands failed — don't block on our check failing
|
|
417
|
+
return { hasPushedWork: false };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
371
420
|
/**
|
|
372
421
|
* Generate a prompt for the agent based on work type
|
|
373
422
|
*
|
|
@@ -671,6 +720,13 @@ IMPORTANT: If you encounter "exceeds maximum allowed tokens" error when reading
|
|
|
671
720
|
- Avoid reading auto-generated files like payload-types.ts (use Grep instead)
|
|
672
721
|
See the "Working with Large Files" section in the project documentation (CLAUDE.md / AGENTS.md) for details.${LINEAR_CLI_INSTRUCTION}`;
|
|
673
722
|
break;
|
|
723
|
+
case 'merge':
|
|
724
|
+
basePrompt = `Handle merge queue operations for ${identifier}.
|
|
725
|
+
Check PR merge readiness (CI status, approvals).
|
|
726
|
+
Attempt rebase onto latest main.
|
|
727
|
+
Resolve conflicts using mergiraf-enhanced git merge if available.
|
|
728
|
+
Push updated branch and trigger merge via configured merge queue provider.${LINEAR_CLI_INSTRUCTION}`;
|
|
729
|
+
break;
|
|
674
730
|
}
|
|
675
731
|
// Inject workflow failure context for retries
|
|
676
732
|
if (options?.failureContext) {
|
|
@@ -698,6 +754,7 @@ const WORK_TYPE_SUFFIX = {
|
|
|
698
754
|
'refinement-coordination': 'REF-COORD',
|
|
699
755
|
'qa-coordination': 'QA-COORD',
|
|
700
756
|
'acceptance-coordination': 'AC-COORD',
|
|
757
|
+
merge: 'MRG',
|
|
701
758
|
};
|
|
702
759
|
/**
|
|
703
760
|
* Generate a worktree identifier that includes the work type suffix
|
|
@@ -761,6 +818,7 @@ export class AgentOrchestrator {
|
|
|
761
818
|
progressLoggers = new Map();
|
|
762
819
|
// Session loggers per agent for verbose analysis logging
|
|
763
820
|
sessionLoggers = new Map();
|
|
821
|
+
contextManagers = new Map();
|
|
764
822
|
// Template registry for configurable workflow prompts
|
|
765
823
|
templateRegistry;
|
|
766
824
|
// Allowlisted project names from .agentfactory/config.yaml
|
|
@@ -781,6 +839,8 @@ export class AgentOrchestrator {
|
|
|
781
839
|
validateCommand;
|
|
782
840
|
// Tool plugin registry for in-process agent tools
|
|
783
841
|
toolRegistry;
|
|
842
|
+
// Merge queue adapter for automated merge operations (initialized from config or repo config)
|
|
843
|
+
mergeQueueAdapter;
|
|
784
844
|
// Git repository root for running git commands (resolved from worktreePath or cwd)
|
|
785
845
|
gitRoot;
|
|
786
846
|
constructor(config = {}, events = {}) {
|
|
@@ -892,12 +952,26 @@ export class AgentOrchestrator {
|
|
|
892
952
|
}
|
|
893
953
|
// Store providers config for per-spawn resolution
|
|
894
954
|
this.configProviders = getProvidersConfig(repoConfig);
|
|
955
|
+
// Initialize merge queue adapter from repository config
|
|
956
|
+
if (repoConfig.mergeQueue?.enabled && !config.mergeQueueAdapter) {
|
|
957
|
+
try {
|
|
958
|
+
this.mergeQueueAdapter = createMergeQueueAdapter(repoConfig.mergeQueue.provider ?? 'github-native');
|
|
959
|
+
console.log(`[orchestrator] Merge queue adapter initialized: ${repoConfig.mergeQueue.provider ?? 'github-native'}`);
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
console.warn(`[orchestrator] Failed to initialize merge queue adapter: ${error instanceof Error ? error.message : String(error)}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
895
965
|
}
|
|
896
966
|
}
|
|
897
967
|
}
|
|
898
968
|
catch (err) {
|
|
899
969
|
console.warn('[orchestrator] Failed to load .agentfactory/config.yaml:', err instanceof Error ? err.message : err);
|
|
900
970
|
}
|
|
971
|
+
// Accept merge queue adapter passed directly via config (takes precedence over repo config)
|
|
972
|
+
if (config.mergeQueueAdapter) {
|
|
973
|
+
this.mergeQueueAdapter = config.mergeQueueAdapter;
|
|
974
|
+
}
|
|
901
975
|
// Initialize tool plugin registry with injected plugins
|
|
902
976
|
this.toolRegistry = new ToolRegistry();
|
|
903
977
|
if (config.toolPlugins) {
|
|
@@ -988,6 +1062,11 @@ export class AgentOrchestrator {
|
|
|
988
1062
|
for (const issue of allIssues) {
|
|
989
1063
|
if (results.length >= maxIssues)
|
|
990
1064
|
break;
|
|
1065
|
+
// Skip sub-issues — coordinators manage their lifecycle, not the backlog scanner
|
|
1066
|
+
if (issue.parentId) {
|
|
1067
|
+
console.log(`[orchestrator] Skipping sub-issue ${issue.identifier} — managed by parent coordinator`);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
991
1070
|
// Filter by allowedProjects from .agentfactory/config.yaml
|
|
992
1071
|
if (this.allowedProjects && this.allowedProjects.length > 0) {
|
|
993
1072
|
if (!issue.projectName || !this.allowedProjects.includes(issue.projectName)) {
|
|
@@ -1423,6 +1502,8 @@ export class AgentOrchestrator {
|
|
|
1423
1502
|
}
|
|
1424
1503
|
// Write helper scripts into .agent/ for agent use
|
|
1425
1504
|
this.writeWorktreeHelpers(worktreePath);
|
|
1505
|
+
// Configure mergiraf merge driver if enabled
|
|
1506
|
+
this.configureMergiraf(worktreePath);
|
|
1426
1507
|
return { worktreePath, worktreeIdentifier };
|
|
1427
1508
|
}
|
|
1428
1509
|
/**
|
|
@@ -1503,6 +1584,39 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1503
1584
|
console.warn(`Failed to write worktree helper scripts: ${error instanceof Error ? error.message : String(error)}`);
|
|
1504
1585
|
}
|
|
1505
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Configure mergiraf as the git merge driver in a worktree.
|
|
1589
|
+
* Falls back silently to default git merge if mergiraf is not installed.
|
|
1590
|
+
*/
|
|
1591
|
+
configureMergiraf(worktreePath) {
|
|
1592
|
+
// Check if mergiraf is disabled via config
|
|
1593
|
+
if (this.repoConfig?.mergeDriver === 'default') {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
try {
|
|
1597
|
+
// Check if mergiraf binary is available
|
|
1598
|
+
execSync('which mergiraf', { stdio: 'pipe', encoding: 'utf-8' });
|
|
1599
|
+
}
|
|
1600
|
+
catch {
|
|
1601
|
+
// mergiraf not installed — fall back to default merge silently
|
|
1602
|
+
console.log('mergiraf not found on PATH, using default git merge driver');
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
try {
|
|
1606
|
+
// Register mergiraf as the merge driver in this worktree
|
|
1607
|
+
// This sets up .git/config merge driver entries and .gitattributes
|
|
1608
|
+
execSync('mergiraf register', {
|
|
1609
|
+
stdio: 'pipe',
|
|
1610
|
+
encoding: 'utf-8',
|
|
1611
|
+
cwd: worktreePath,
|
|
1612
|
+
});
|
|
1613
|
+
console.log(`mergiraf registered as merge driver in ${worktreePath}`);
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
// Log warning but don't fail — merge driver is non-critical
|
|
1617
|
+
console.warn(`Failed to register mergiraf in worktree: ${error instanceof Error ? error.message : String(error)}`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1506
1620
|
/**
|
|
1507
1621
|
* Link dependencies from the main repo into a worktree via symlinks.
|
|
1508
1622
|
*
|
|
@@ -1793,6 +1907,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1793
1907
|
this.sessionLoggers.set(issueId, sessionLogger);
|
|
1794
1908
|
log.debug('Session logging initialized', { logsDir: logConfig.logsDir });
|
|
1795
1909
|
}
|
|
1910
|
+
// Initialize context manager for context window management
|
|
1911
|
+
const contextManager = ContextManager.load(worktreePath);
|
|
1912
|
+
this.contextManagers.set(issueId, contextManager);
|
|
1796
1913
|
log.debug('State persistence initialized', { agentDir: resolve(worktreePath, '.agent') });
|
|
1797
1914
|
}
|
|
1798
1915
|
catch (stateError) {
|
|
@@ -1996,6 +2113,41 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1996
2113
|
if (emitter) {
|
|
1997
2114
|
await emitter.flush();
|
|
1998
2115
|
}
|
|
2116
|
+
// Post-exit PR detection: if the agent exited without a detected PR URL,
|
|
2117
|
+
// check GitHub directly in case the PR was created but the output wasn't captured
|
|
2118
|
+
if (agent.status === 'completed' && !agent.pullRequestUrl && agent.worktreePath) {
|
|
2119
|
+
const postExitWorkType = agent.workType ?? 'development';
|
|
2120
|
+
const isPostExitCodeProducing = postExitWorkType === 'development' || postExitWorkType === 'inflight';
|
|
2121
|
+
if (isPostExitCodeProducing) {
|
|
2122
|
+
try {
|
|
2123
|
+
const currentBranch = execSync('git branch --show-current', {
|
|
2124
|
+
cwd: agent.worktreePath,
|
|
2125
|
+
encoding: 'utf-8',
|
|
2126
|
+
timeout: 10000,
|
|
2127
|
+
}).trim();
|
|
2128
|
+
if (currentBranch && currentBranch !== 'main' && currentBranch !== 'master') {
|
|
2129
|
+
const prJson = execSync(`gh pr list --head "${currentBranch}" --json url --limit 1`, {
|
|
2130
|
+
cwd: agent.worktreePath,
|
|
2131
|
+
encoding: 'utf-8',
|
|
2132
|
+
timeout: 15000,
|
|
2133
|
+
}).trim();
|
|
2134
|
+
const prs = JSON.parse(prJson);
|
|
2135
|
+
if (prs.length > 0 && prs[0].url) {
|
|
2136
|
+
log?.info('Post-exit PR detection found existing PR', { prUrl: prs[0].url, branch: currentBranch });
|
|
2137
|
+
agent.pullRequestUrl = prs[0].url;
|
|
2138
|
+
if (sessionId) {
|
|
2139
|
+
await this.updateSessionPullRequest(sessionId, prs[0].url, agent);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
catch (error) {
|
|
2145
|
+
log?.debug('Post-exit PR detection failed (non-fatal)', {
|
|
2146
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
1999
2151
|
// Update Linear status based on work type if auto-transition is enabled
|
|
2000
2152
|
if (agent.status === 'completed' && this.config.autoTransition) {
|
|
2001
2153
|
const workType = agent.workType ?? 'development';
|
|
@@ -2073,8 +2225,32 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2073
2225
|
// Do NOT set targetStatus — leave issue in current state
|
|
2074
2226
|
}
|
|
2075
2227
|
else {
|
|
2076
|
-
//
|
|
2077
|
-
|
|
2228
|
+
// Worktree is clean (no uncommitted/unpushed changes) — but check if branch
|
|
2229
|
+
// has commits ahead of main that should have resulted in a PR
|
|
2230
|
+
const hasPushedWork = checkForPushedWorkWithoutPR(agent.worktreePath);
|
|
2231
|
+
if (hasPushedWork.hasPushedWork) {
|
|
2232
|
+
// Agent pushed commits to remote but never created a PR — block promotion
|
|
2233
|
+
log?.error('Code-producing agent pushed commits but no PR was created — blocking promotion', {
|
|
2234
|
+
workType,
|
|
2235
|
+
details: hasPushedWork.details,
|
|
2236
|
+
});
|
|
2237
|
+
try {
|
|
2238
|
+
await this.client.createComment(issueId, `⚠️ **Agent completed and pushed code, but no PR was created.**\n\n` +
|
|
2239
|
+
`${hasPushedWork.details}\n\n` +
|
|
2240
|
+
`**Issue status was NOT promoted** because work cannot be reviewed without a PR.\n\n` +
|
|
2241
|
+
`The branch has been pushed to the remote. To recover:\n` +
|
|
2242
|
+
`\`\`\`bash\ngh pr create --head ${hasPushedWork.branch} --title "feat: <title>" --body "..."\n\`\`\`\n` +
|
|
2243
|
+
`Or re-trigger the agent to complete the PR creation step.`);
|
|
2244
|
+
}
|
|
2245
|
+
catch {
|
|
2246
|
+
// Best-effort comment
|
|
2247
|
+
}
|
|
2248
|
+
// Do NOT set targetStatus — leave issue in current state
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
// No PR and no pushed commits ahead of main — genuinely clean completion
|
|
2252
|
+
targetStatus = this.statusMappings.workTypeCompleteStatus[workType];
|
|
2253
|
+
}
|
|
2078
2254
|
}
|
|
2079
2255
|
}
|
|
2080
2256
|
else {
|
|
@@ -2096,6 +2272,28 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2096
2272
|
else if (!isResultSensitive) {
|
|
2097
2273
|
log?.info('No auto-transition configured for work type', { workType });
|
|
2098
2274
|
}
|
|
2275
|
+
// Merge queue: enqueue PR after successful merge work
|
|
2276
|
+
if (workType === 'merge' && this.mergeQueueAdapter && agent.pullRequestUrl) {
|
|
2277
|
+
try {
|
|
2278
|
+
const prMatch = agent.pullRequestUrl.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
2279
|
+
if (prMatch) {
|
|
2280
|
+
const [, owner, repo, prNum] = prMatch;
|
|
2281
|
+
const canEnqueue = await this.mergeQueueAdapter.canEnqueue(owner, repo, parseInt(prNum, 10));
|
|
2282
|
+
if (canEnqueue) {
|
|
2283
|
+
const status = await this.mergeQueueAdapter.enqueue(owner, repo, parseInt(prNum, 10));
|
|
2284
|
+
log?.info('PR enqueued in merge queue', { owner, repo, prNumber: prNum, state: status.state });
|
|
2285
|
+
}
|
|
2286
|
+
else {
|
|
2287
|
+
log?.info('PR not eligible for merge queue', { owner, repo, prNumber: prNum });
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
catch (error) {
|
|
2292
|
+
log?.warn('Failed to enqueue PR in merge queue', {
|
|
2293
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2099
2297
|
// Unassign agent from issue for clean handoff visibility
|
|
2100
2298
|
// This enables automated QA pickup via webhook
|
|
2101
2299
|
// Skip unassignment for research work (user should decide when to move to backlog)
|
|
@@ -2295,6 +2493,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2295
2493
|
}
|
|
2296
2494
|
else if (event.subtype === 'compact_boundary') {
|
|
2297
2495
|
log?.debug('Context compacted');
|
|
2496
|
+
// Trigger incremental summarization on compaction boundary
|
|
2497
|
+
this.contextManagers.get(issueId)?.handleCompaction();
|
|
2298
2498
|
}
|
|
2299
2499
|
else if (event.subtype === 'hook_response') {
|
|
2300
2500
|
// Provider-specific hook handling — access raw event for details
|
|
@@ -2315,6 +2515,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2315
2515
|
case 'tool_result':
|
|
2316
2516
|
// Tool results — track activity and detect PR URLs
|
|
2317
2517
|
this.updateLastActivity(issueId, 'tool_result');
|
|
2518
|
+
// Feed to context manager for artifact tracking
|
|
2519
|
+
this.contextManagers.get(issueId)?.processEvent(event);
|
|
2318
2520
|
sessionLogger?.logToolResult(event.toolUseId ?? 'unknown', event.content, event.isError);
|
|
2319
2521
|
// Detect GitHub PR URLs in tool output (from gh pr create)
|
|
2320
2522
|
if (sessionId) {
|
|
@@ -2329,8 +2531,19 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2329
2531
|
case 'assistant_text':
|
|
2330
2532
|
// Assistant text output
|
|
2331
2533
|
this.updateLastActivity(issueId, 'assistant');
|
|
2534
|
+
// Feed to context manager for session intent extraction
|
|
2535
|
+
this.contextManagers.get(issueId)?.processEvent(event);
|
|
2332
2536
|
heartbeatWriter?.recordThinking();
|
|
2333
2537
|
sessionLogger?.logAssistant(event.text);
|
|
2538
|
+
// Detect GitHub PR URLs in assistant text (backup for tool_result detection)
|
|
2539
|
+
if (sessionId && !agent.pullRequestUrl) {
|
|
2540
|
+
const prUrl = this.extractPullRequestUrl(event.text);
|
|
2541
|
+
if (prUrl) {
|
|
2542
|
+
log?.info('Pull request detected in assistant text', { prUrl });
|
|
2543
|
+
agent.pullRequestUrl = prUrl;
|
|
2544
|
+
await this.updateSessionPullRequest(sessionId, prUrl, agent);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2334
2547
|
if (emitter) {
|
|
2335
2548
|
await emitter.emitThought(event.text.substring(0, 200));
|
|
2336
2549
|
}
|
|
@@ -2338,6 +2551,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2338
2551
|
case 'tool_use':
|
|
2339
2552
|
// Tool invocation
|
|
2340
2553
|
this.updateLastActivity(issueId, 'assistant');
|
|
2554
|
+
// Feed to context manager for artifact tracking
|
|
2555
|
+
this.contextManagers.get(issueId)?.processEvent(event);
|
|
2341
2556
|
log?.toolCall(event.toolName, event.input);
|
|
2342
2557
|
heartbeatWriter?.recordToolCall(event.toolName);
|
|
2343
2558
|
progressLogger?.logTool(event.toolName, event.input);
|
|
@@ -2383,6 +2598,15 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2383
2598
|
// Store full result for completion comment posting later
|
|
2384
2599
|
if (event.message) {
|
|
2385
2600
|
agent.resultMessage = event.message;
|
|
2601
|
+
// Detect GitHub PR URLs in final result message (backup for tool_result detection)
|
|
2602
|
+
if (sessionId && !agent.pullRequestUrl) {
|
|
2603
|
+
const prUrl = this.extractPullRequestUrl(event.message);
|
|
2604
|
+
if (prUrl) {
|
|
2605
|
+
log?.info('Pull request detected in result message', { prUrl });
|
|
2606
|
+
agent.pullRequestUrl = prUrl;
|
|
2607
|
+
await this.updateSessionPullRequest(sessionId, prUrl, agent);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2386
2610
|
}
|
|
2387
2611
|
// Update state to completing/completed (only for worktree-based agents)
|
|
2388
2612
|
if (agent.worktreePath) {
|
|
@@ -2432,6 +2656,22 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2432
2656
|
}
|
|
2433
2657
|
progressLogger?.logError('Agent error result', new Error(errorMessage));
|
|
2434
2658
|
sessionLogger?.logError('Agent error result', new Error(errorMessage), { subtype: event.errorSubtype });
|
|
2659
|
+
// Merge queue: dequeue PR on merge agent failure
|
|
2660
|
+
if (agent.workType === 'merge' && this.mergeQueueAdapter && agent.pullRequestUrl) {
|
|
2661
|
+
try {
|
|
2662
|
+
const prMatch = agent.pullRequestUrl.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
2663
|
+
if (prMatch) {
|
|
2664
|
+
const [, owner, repo, prNum] = prMatch;
|
|
2665
|
+
await this.mergeQueueAdapter.dequeue(owner, repo, parseInt(prNum, 10));
|
|
2666
|
+
log?.info('PR dequeued from merge queue after failure', { owner, repo, prNumber: prNum });
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
catch (dequeueError) {
|
|
2670
|
+
log?.warn('Failed to dequeue PR from merge queue', {
|
|
2671
|
+
error: dequeueError instanceof Error ? dequeueError.message : String(dequeueError),
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2435
2675
|
// Report tool errors as Linear issues for tracking
|
|
2436
2676
|
// Only report for 'error_during_execution' subtype (tool/execution errors)
|
|
2437
2677
|
if (event.errorSubtype === 'error_during_execution' &&
|
|
@@ -2619,6 +2859,17 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2619
2859
|
progressLogger.stop();
|
|
2620
2860
|
this.progressLoggers.delete(issueId);
|
|
2621
2861
|
}
|
|
2862
|
+
// Persist and cleanup context manager
|
|
2863
|
+
const contextManager = this.contextManagers.get(issueId);
|
|
2864
|
+
if (contextManager) {
|
|
2865
|
+
try {
|
|
2866
|
+
contextManager.persist();
|
|
2867
|
+
}
|
|
2868
|
+
catch {
|
|
2869
|
+
// Ignore persistence errors during cleanup
|
|
2870
|
+
}
|
|
2871
|
+
this.contextManagers.delete(issueId);
|
|
2872
|
+
}
|
|
2622
2873
|
// Session logger is cleaned up separately (in finalizeSessionLogger)
|
|
2623
2874
|
// to ensure the final status is captured before cleanup
|
|
2624
2875
|
this.sessionLoggers.delete(issueId);
|
|
@@ -2722,6 +2973,25 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2722
2973
|
throw new Error(`Issue ${identifier} is in terminal status '${currentStatus}' — skipping ${workType ?? 'auto'} work. ` +
|
|
2723
2974
|
`The issue was likely accepted/canceled after being queued.`);
|
|
2724
2975
|
}
|
|
2976
|
+
// Guard: skip sub-issues that should be managed by a coordinator, not spawned independently.
|
|
2977
|
+
// Only applies when no explicit work type is provided (i.e., orchestrator auto-pickup).
|
|
2978
|
+
// Coordinators spawning sub-agents always pass an explicit work type, so they bypass this check.
|
|
2979
|
+
if (!workType) {
|
|
2980
|
+
try {
|
|
2981
|
+
const isChild = await this.client.isChildIssue(issueId);
|
|
2982
|
+
if (isChild) {
|
|
2983
|
+
throw new Error(`Issue ${identifier} is a sub-issue managed by a parent coordinator — skipping independent pickup. ` +
|
|
2984
|
+
`Sub-issues should only be processed by their parent's coordination agent.`);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
catch (err) {
|
|
2988
|
+
// Re-throw our own guard error; swallow API errors so we don't block on transient failures
|
|
2989
|
+
if (err instanceof Error && err.message.includes('managed by a parent coordinator')) {
|
|
2990
|
+
throw err;
|
|
2991
|
+
}
|
|
2992
|
+
console.warn(`Failed to check child status for ${identifier}:`, err);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2725
2995
|
// Defense in depth: re-validate git remote before spawning (guards against long-running instances)
|
|
2726
2996
|
if (this.config.repository) {
|
|
2727
2997
|
validateGitRemote(this.config.repository, this.gitRoot);
|
|
@@ -2830,6 +3100,13 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2830
3100
|
labels: labelNames,
|
|
2831
3101
|
});
|
|
2832
3102
|
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Get the merge queue adapter, if configured.
|
|
3105
|
+
* Returns undefined if no merge queue is enabled.
|
|
3106
|
+
*/
|
|
3107
|
+
getMergeQueueAdapter() {
|
|
3108
|
+
return this.mergeQueueAdapter;
|
|
3109
|
+
}
|
|
2833
3110
|
/**
|
|
2834
3111
|
* Get all active agents
|
|
2835
3112
|
*/
|
|
@@ -3214,6 +3491,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3214
3491
|
this.sessionLoggers.set(issueId, sessionLogger);
|
|
3215
3492
|
log.debug('Session logging initialized', { logsDir: logConfig.logsDir });
|
|
3216
3493
|
}
|
|
3494
|
+
// Initialize context manager for context window management
|
|
3495
|
+
const contextManager = ContextManager.load(worktreePath);
|
|
3496
|
+
this.contextManagers.set(issueId, contextManager);
|
|
3217
3497
|
log.debug('State persistence initialized', { agentDir: resolve(worktreePath, '.agent') });
|
|
3218
3498
|
}
|
|
3219
3499
|
catch (stateError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6GjD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,aAAa,GACtB,eAAe,CAyCjB"}
|
|
@@ -91,6 +91,12 @@ const COORDINATION_FAIL_PATTERNS = [
|
|
|
91
91
|
// Explicit result labels
|
|
92
92
|
/\bCoordination\s+(?:Result|Status|Verdict):\s*\*{0,2}Fail(?:ed)?\*{0,2}/i,
|
|
93
93
|
/\bCoordination\s+Failed/i,
|
|
94
|
+
// Early-exit detection — agent reported progress instead of completing coordination
|
|
95
|
+
/\bI'll wait\b.*\bcomplete\b/i,
|
|
96
|
+
/\bagents?\s+(?:are|is)\s+(?:actively\s+)?(?:working|running|in\s+progress)\b/i,
|
|
97
|
+
/\bwork\s+is\s+in\s+progress\b/i,
|
|
98
|
+
/\bwait(?:ing)?\s+for\s+(?:them|sub-?\s*agents?|sub-?\s*issues?)\s+to\s+(?:complete|finish)\b/i,
|
|
99
|
+
/\bbefore\s+proceeding\s+to\s+(?:Layer|Wave|Phase)\s+\d/i,
|
|
94
100
|
];
|
|
95
101
|
/**
|
|
96
102
|
* Parse the agent's result message to determine whether the work passed or failed.
|
|
@@ -192,6 +192,25 @@ describe('parseWorkResult', () => {
|
|
|
192
192
|
it('checks fail patterns before pass patterns', () => {
|
|
193
193
|
expect(parseWorkResult('All 8/8 sub-issues completed.\n### Must Fix Before Merge\n1. Bad code', 'coordination')).toBe('failed');
|
|
194
194
|
});
|
|
195
|
+
// Early-exit detection — agent reported progress instead of completing
|
|
196
|
+
it('detects "I\'ll wait for them to complete" as early-exit fail', () => {
|
|
197
|
+
expect(parseWorkResult("Both agents are progressing well. I'll wait for them to complete before proceeding.", 'coordination')).toBe('failed');
|
|
198
|
+
});
|
|
199
|
+
it('detects "agents are actively working" as early-exit fail', () => {
|
|
200
|
+
expect(parseWorkResult('SUP-1552 agent has read the existing patterns. Agents are actively working on their sub-issues.', 'coordination')).toBe('failed');
|
|
201
|
+
});
|
|
202
|
+
it('detects "waiting for sub-agents to finish" as early-exit fail', () => {
|
|
203
|
+
expect(parseWorkResult('Spawned 2 agents. Waiting for sub-agents to finish their work.', 'coordination')).toBe('failed');
|
|
204
|
+
});
|
|
205
|
+
it('detects "before proceeding to Layer 2" as early-exit fail', () => {
|
|
206
|
+
expect(parseWorkResult('Wave 1 agents spawned. Will check results before proceeding to Layer 2.', 'coordination')).toBe('failed');
|
|
207
|
+
});
|
|
208
|
+
it('detects "work is in progress" as early-exit fail', () => {
|
|
209
|
+
expect(parseWorkResult('Work is in progress on 3 sub-issues. Status will be updated.', 'coordination')).toBe('failed');
|
|
210
|
+
});
|
|
211
|
+
it('early-exit patterns also apply to inflight-coordination', () => {
|
|
212
|
+
expect(parseWorkResult("Agents are actively running. I'll wait for them to complete.", 'inflight-coordination')).toBe('failed');
|
|
213
|
+
});
|
|
195
214
|
});
|
|
196
215
|
// QA coordination with real agent output formats
|
|
197
216
|
describe('QA coordination real-world patterns', () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects and recovers agent state from the .agent/ directory.
|
|
5
5
|
* Enables crash recovery and duplicate agent prevention.
|
|
6
6
|
*/
|
|
7
|
-
import type { WorktreeState, HeartbeatState, TodosState, RecoveryCheckResult } from './state-types.js';
|
|
7
|
+
import type { WorktreeState, HeartbeatState, TodosState, RecoveryCheckResult, StructuredSummary } from './state-types.js';
|
|
8
8
|
import type { AgentWorkType } from './work-types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Get the .agent directory path for a worktree
|
|
@@ -84,7 +84,13 @@ export declare function getTaskListId(issueIdentifier: string, workType: AgentWo
|
|
|
84
84
|
/**
|
|
85
85
|
* Build a recovery prompt for resuming crashed work
|
|
86
86
|
*/
|
|
87
|
-
export declare function buildRecoveryPrompt(state: WorktreeState, todos?: TodosState): string;
|
|
87
|
+
export declare function buildRecoveryPrompt(state: WorktreeState, todos?: TodosState, contextSection?: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* Build a context injection string for session resume (non-crash).
|
|
90
|
+
* Returns empty string if no context is available.
|
|
91
|
+
* Uses neutral framing to avoid "context anxiety" (from SUP-1186 research).
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildResumeContext(contextSection: string): string;
|
|
88
94
|
/**
|
|
89
95
|
* Parse environment variable for heartbeat timeout
|
|
90
96
|
*/
|
|
@@ -93,4 +99,17 @@ export declare function getHeartbeatTimeoutFromEnv(): number;
|
|
|
93
99
|
* Parse environment variable for max recovery attempts
|
|
94
100
|
*/
|
|
95
101
|
export declare function getMaxRecoveryAttemptsFromEnv(): number;
|
|
102
|
+
/**
|
|
103
|
+
* Get the path to the summary.json file
|
|
104
|
+
*/
|
|
105
|
+
export declare function getSummaryPath(worktreePath: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Read the persisted structured summary from a worktree
|
|
108
|
+
*/
|
|
109
|
+
export declare function readSummary(worktreePath: string): StructuredSummary | null;
|
|
110
|
+
/**
|
|
111
|
+
* Write the structured summary with atomic writes (write to temp, then rename).
|
|
112
|
+
* This ensures the summary survives process crashes.
|
|
113
|
+
*/
|
|
114
|
+
export declare function writeSummary(worktreePath: string, summary: StructuredSummary): void;
|
|
96
115
|
//# sourceMappingURL=state-recovery.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-recovery.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,mBAAmB,
|
|
1
|
+
{"version":3,"file":"state-recovery.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAQpD;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAeD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,cAAc,GAAG,IAAI,EAChC,SAAS,GAAE,MAAqC,GAC/C,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAEzE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CACxB,GACL,mBAAmB,CA2ErB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAK7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAI3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAC9B,aAAa,GAAG,IAAI,CAWtB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAIxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB,GAAG,aAAa,CAmBhB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,aAAa,GACtB,MAAM,CAiBR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,UAAU,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CA6DR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CASjE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAE1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAMnF"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects and recovers agent state from the .agent/ directory.
|
|
5
5
|
* Enables crash recovery and duplicate agent prevention.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
|
|
8
8
|
import { resolve } from 'path';
|
|
9
9
|
// Default heartbeat timeout: 30 seconds
|
|
10
10
|
const DEFAULT_HEARTBEAT_TIMEOUT_MS = 30000;
|
|
@@ -230,14 +230,23 @@ export function getTaskListId(issueIdentifier, workType) {
|
|
|
230
230
|
'refinement-coordination': 'REF-COORD',
|
|
231
231
|
'qa-coordination': 'QA-COORD',
|
|
232
232
|
'acceptance-coordination': 'AC-COORD',
|
|
233
|
+
merge: 'MRG',
|
|
233
234
|
};
|
|
234
235
|
return `${issueIdentifier}-${suffixMap[workType]}`;
|
|
235
236
|
}
|
|
236
237
|
/**
|
|
237
238
|
* Build a recovery prompt for resuming crashed work
|
|
238
239
|
*/
|
|
239
|
-
export function buildRecoveryPrompt(state, todos) {
|
|
240
|
+
export function buildRecoveryPrompt(state, todos, contextSection) {
|
|
240
241
|
const lines = [];
|
|
242
|
+
// Inject structured summary context at the top (if available)
|
|
243
|
+
if (contextSection) {
|
|
244
|
+
lines.push(contextSection);
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push('---');
|
|
247
|
+
lines.push('You are resuming work on this task. The context above summarizes your progress so far.');
|
|
248
|
+
lines.push('');
|
|
249
|
+
}
|
|
241
250
|
lines.push(`Resume work on ${state.issueIdentifier}.`);
|
|
242
251
|
lines.push('');
|
|
243
252
|
lines.push('RECOVERY CONTEXT:');
|
|
@@ -271,10 +280,30 @@ export function buildRecoveryPrompt(state, todos) {
|
|
|
271
280
|
lines.push('3. Review the codebase for any partial changes');
|
|
272
281
|
lines.push('4. Continue from where the previous session left off');
|
|
273
282
|
lines.push('5. If work appears complete, verify and create PR if needed');
|
|
283
|
+
// Repeat key context at the end to mitigate "lost in the middle" phenomenon
|
|
284
|
+
if (contextSection) {
|
|
285
|
+
lines.push('');
|
|
286
|
+
lines.push('KEY CONTEXT REMINDER:');
|
|
287
|
+
lines.push(contextSection);
|
|
288
|
+
}
|
|
274
289
|
lines.push('');
|
|
275
290
|
lines.push(`Original prompt: ${state.prompt}`);
|
|
276
291
|
return lines.join('\n');
|
|
277
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Build a context injection string for session resume (non-crash).
|
|
295
|
+
* Returns empty string if no context is available.
|
|
296
|
+
* Uses neutral framing to avoid "context anxiety" (from SUP-1186 research).
|
|
297
|
+
*/
|
|
298
|
+
export function buildResumeContext(contextSection) {
|
|
299
|
+
if (!contextSection)
|
|
300
|
+
return '';
|
|
301
|
+
const lines = [];
|
|
302
|
+
lines.push('Here is your session context:');
|
|
303
|
+
lines.push('');
|
|
304
|
+
lines.push(contextSection);
|
|
305
|
+
return lines.join('\n');
|
|
306
|
+
}
|
|
278
307
|
/**
|
|
279
308
|
* Parse environment variable for heartbeat timeout
|
|
280
309
|
*/
|
|
@@ -301,3 +330,26 @@ export function getMaxRecoveryAttemptsFromEnv() {
|
|
|
301
330
|
}
|
|
302
331
|
return DEFAULT_MAX_RECOVERY_ATTEMPTS;
|
|
303
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Get the path to the summary.json file
|
|
335
|
+
*/
|
|
336
|
+
export function getSummaryPath(worktreePath) {
|
|
337
|
+
return resolve(getAgentDir(worktreePath), 'summary.json');
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Read the persisted structured summary from a worktree
|
|
341
|
+
*/
|
|
342
|
+
export function readSummary(worktreePath) {
|
|
343
|
+
return readJsonSafe(getSummaryPath(worktreePath));
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Write the structured summary with atomic writes (write to temp, then rename).
|
|
347
|
+
* This ensures the summary survives process crashes.
|
|
348
|
+
*/
|
|
349
|
+
export function writeSummary(worktreePath, summary) {
|
|
350
|
+
const summaryPath = getSummaryPath(worktreePath);
|
|
351
|
+
initializeAgentDir(worktreePath);
|
|
352
|
+
const tempPath = summaryPath + '.tmp';
|
|
353
|
+
writeFileSync(tempPath, JSON.stringify(summary, null, 2));
|
|
354
|
+
renameSync(tempPath, summaryPath);
|
|
355
|
+
}
|