@renseiai/agentfactory 0.8.13 → 0.8.15
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/orchestrator/completion-contracts.d.ts +89 -0
- package/dist/src/orchestrator/completion-contracts.d.ts.map +1 -0
- package/dist/src/orchestrator/completion-contracts.js +228 -0
- package/dist/src/orchestrator/completion-contracts.test.d.ts +2 -0
- package/dist/src/orchestrator/completion-contracts.test.d.ts.map +1 -0
- package/dist/src/orchestrator/completion-contracts.test.js +195 -0
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +32 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +178 -26
- package/dist/src/orchestrator/session-backstop.d.ts +67 -0
- package/dist/src/orchestrator/session-backstop.d.ts.map +1 -0
- package/dist/src/orchestrator/session-backstop.js +394 -0
- package/dist/src/orchestrator/session-backstop.test.d.ts +2 -0
- package/dist/src/orchestrator/session-backstop.test.d.ts.map +1 -0
- package/dist/src/orchestrator/session-backstop.test.js +245 -0
- package/dist/src/orchestrator/worktree-checks.test.d.ts +2 -0
- package/dist/src/orchestrator/worktree-checks.test.d.ts.map +1 -0
- package/dist/src/orchestrator/worktree-checks.test.js +159 -0
- package/dist/src/providers/a2a-provider.d.ts +4 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -1
- package/dist/src/providers/a2a-provider.js +4 -0
- package/dist/src/providers/amp-provider.d.ts +4 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -1
- package/dist/src/providers/amp-provider.js +4 -0
- package/dist/src/providers/claude-provider.d.ts +4 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.js +6 -0
- package/dist/src/providers/codex-provider.d.ts +4 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-provider.js +4 -0
- package/dist/src/providers/index.d.ts +1 -1
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/spring-ai-provider.d.ts +4 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -1
- package/dist/src/providers/spring-ai-provider.js +4 -0
- package/dist/src/providers/types.d.ts +22 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/templates/types.d.ts +3 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +2 -0
- package/dist/src/tools/index.d.ts +1 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/registry.d.ts +6 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +5 -1
- package/package.json +2 -2
|
@@ -16,6 +16,7 @@ import { createSessionLogger } from './session-logger.js';
|
|
|
16
16
|
import { ContextManager } from './context-manager.js';
|
|
17
17
|
import { isSessionLoggingEnabled, getLogAnalysisConfig } from './log-config.js';
|
|
18
18
|
import { parseWorkResult } from './parse-work-result.js';
|
|
19
|
+
import { runBackstop, formatBackstopComment } from './session-backstop.js';
|
|
19
20
|
import { createActivityEmitter } from './activity-emitter.js';
|
|
20
21
|
import { createApiActivityEmitter } from './api-activity-emitter.js';
|
|
21
22
|
import { createLogger } from '../logger.js';
|
|
@@ -303,7 +304,7 @@ function extractToolNameFromError(error) {
|
|
|
303
304
|
* @param worktreePath - Path to the git worktree
|
|
304
305
|
* @returns Check result with reason if incomplete work is found
|
|
305
306
|
*/
|
|
306
|
-
function checkForIncompleteWork(worktreePath) {
|
|
307
|
+
export function checkForIncompleteWork(worktreePath) {
|
|
307
308
|
try {
|
|
308
309
|
// Check for uncommitted changes (staged or unstaged)
|
|
309
310
|
const statusOutput = execSync('git status --porcelain', {
|
|
@@ -358,15 +359,12 @@ function checkForIncompleteWork(worktreePath) {
|
|
|
358
359
|
encoding: 'utf-8',
|
|
359
360
|
timeout: 10000,
|
|
360
361
|
}).trim();
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// Remote branch exists, no issue
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
362
|
+
const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
|
|
363
|
+
cwd: worktreePath,
|
|
364
|
+
encoding: 'utf-8',
|
|
365
|
+
timeout: 10000,
|
|
366
|
+
}).trim();
|
|
367
|
+
if (remoteRef.length === 0) {
|
|
370
368
|
// Remote branch doesn't exist - branch never pushed
|
|
371
369
|
return {
|
|
372
370
|
hasIncompleteWork: true,
|
|
@@ -391,7 +389,7 @@ function checkForIncompleteWork(worktreePath) {
|
|
|
391
389
|
};
|
|
392
390
|
}
|
|
393
391
|
}
|
|
394
|
-
function checkForPushedWorkWithoutPR(worktreePath) {
|
|
392
|
+
export function checkForPushedWorkWithoutPR(worktreePath) {
|
|
395
393
|
try {
|
|
396
394
|
const currentBranch = execSync('git branch --show-current', {
|
|
397
395
|
cwd: worktreePath,
|
|
@@ -840,6 +838,8 @@ export class AgentOrchestrator {
|
|
|
840
838
|
// Session loggers per agent for verbose analysis logging
|
|
841
839
|
sessionLoggers = new Map();
|
|
842
840
|
contextManagers = new Map();
|
|
841
|
+
// Session output flags for completion contract validation (keyed by issueId)
|
|
842
|
+
sessionOutputFlags = new Map();
|
|
843
843
|
// Template registry for configurable workflow prompts
|
|
844
844
|
templateRegistry;
|
|
845
845
|
// Allowlisted project names from .agentfactory/config.yaml
|
|
@@ -1287,6 +1287,38 @@ export class AgentOrchestrator {
|
|
|
1287
1287
|
return false;
|
|
1288
1288
|
}
|
|
1289
1289
|
}
|
|
1290
|
+
// SAFETY GUARD 4: Preserved worktrees — save work as patch, then allow cleanup.
|
|
1291
|
+
// Preserved worktrees contain uncommitted work from a previous agent session.
|
|
1292
|
+
// A diagnostic comment was already posted to the issue when the worktree was
|
|
1293
|
+
// preserved. Blocking all future agents on this branch indefinitely causes
|
|
1294
|
+
// work stoppages, so we save a patch for manual recovery and allow cleanup.
|
|
1295
|
+
const preservedMarker = resolve(conflictPath, '.agent', 'preserved.json');
|
|
1296
|
+
if (existsSync(preservedMarker)) {
|
|
1297
|
+
console.warn(`Preserved worktree detected at ${conflictPath}. ` +
|
|
1298
|
+
`Saving incomplete work as patch before cleanup to unblock branch '${branchName}'.`);
|
|
1299
|
+
try {
|
|
1300
|
+
const patchDir = resolve(resolveWorktreePath(this.config.worktreePath, this.gitRoot), '.patches');
|
|
1301
|
+
if (!existsSync(patchDir)) {
|
|
1302
|
+
mkdirSync(patchDir, { recursive: true });
|
|
1303
|
+
}
|
|
1304
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1305
|
+
const patchName = `${branchName}-preserved-${timestamp}.patch`;
|
|
1306
|
+
const patchPath = resolve(patchDir, patchName);
|
|
1307
|
+
const diff = execSync('git diff HEAD', {
|
|
1308
|
+
cwd: conflictPath,
|
|
1309
|
+
encoding: 'utf-8',
|
|
1310
|
+
timeout: 10000,
|
|
1311
|
+
});
|
|
1312
|
+
if (diff.trim().length > 0) {
|
|
1313
|
+
writeFileSync(patchPath, diff);
|
|
1314
|
+
console.log(`Saved preserved worktree patch: ${patchPath}`);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
catch (patchError) {
|
|
1318
|
+
console.warn('Failed to save preserved worktree patch:', patchError instanceof Error ? patchError.message : String(patchError));
|
|
1319
|
+
}
|
|
1320
|
+
// Fall through to cleanup below (don't return false)
|
|
1321
|
+
}
|
|
1290
1322
|
// Check if the agent in the conflicting worktree is still alive
|
|
1291
1323
|
const recoveryInfo = checkRecovery(conflictPath, {
|
|
1292
1324
|
heartbeatTimeoutMs: getHeartbeatTimeoutFromEnv(),
|
|
@@ -1899,6 +1931,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1899
1931
|
buildCommand: perProject?.buildCommand ?? this.buildCommand,
|
|
1900
1932
|
testCommand: perProject?.testCommand ?? this.testCommand,
|
|
1901
1933
|
validateCommand: perProject?.validateCommand ?? this.validateCommand,
|
|
1934
|
+
agentBugBacklog: process.env.AGENT_BUG_BACKLOG || undefined,
|
|
1902
1935
|
};
|
|
1903
1936
|
const rendered = this.templateRegistry.renderPrompt(workType, context);
|
|
1904
1937
|
prompt = rendered ?? generatePromptForWorkType(identifier, workType);
|
|
@@ -2080,7 +2113,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2080
2113
|
}
|
|
2081
2114
|
log.info('Starting agent via provider', { provider: spawnProviderName, source: providerSource, cwd: worktreePath ?? 'repo-root', workType, promptPreview: prompt.substring(0, 50) });
|
|
2082
2115
|
// Create in-process tool servers from registered plugins
|
|
2083
|
-
const
|
|
2116
|
+
const toolServers = spawnProviderName === 'claude'
|
|
2084
2117
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
2085
2118
|
: undefined;
|
|
2086
2119
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -2096,7 +2129,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2096
2129
|
abortController,
|
|
2097
2130
|
autonomous: true,
|
|
2098
2131
|
sandboxEnabled: this.config.sandboxEnabled,
|
|
2099
|
-
mcpServers,
|
|
2132
|
+
mcpServers: toolServers?.servers,
|
|
2133
|
+
mcpToolNames: toolServers?.toolNames,
|
|
2100
2134
|
maxTurns,
|
|
2101
2135
|
onProcessSpawned: (pid) => {
|
|
2102
2136
|
agent.pid = pid;
|
|
@@ -2213,6 +2247,43 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2213
2247
|
}
|
|
2214
2248
|
}
|
|
2215
2249
|
}
|
|
2250
|
+
// --- Session Backstop: Validate completion contract and recover missing outputs ---
|
|
2251
|
+
if (agent.status === 'completed') {
|
|
2252
|
+
const outputFlags = this.sessionOutputFlags.get(issueId);
|
|
2253
|
+
const backstopCtx = {
|
|
2254
|
+
agent,
|
|
2255
|
+
commentPosted: outputFlags?.commentPosted ?? false,
|
|
2256
|
+
issueUpdated: outputFlags?.issueUpdated ?? false,
|
|
2257
|
+
subIssuesCreated: outputFlags?.subIssuesCreated ?? false,
|
|
2258
|
+
};
|
|
2259
|
+
const backstopResult = runBackstop(backstopCtx);
|
|
2260
|
+
if (backstopResult.backstop.actions.length > 0) {
|
|
2261
|
+
log?.info('Session backstop ran', {
|
|
2262
|
+
actions: backstopResult.backstop.actions.map(a => `${a.field}:${a.success ? 'ok' : 'fail'}`),
|
|
2263
|
+
fullyRecovered: backstopResult.backstop.fullyRecovered,
|
|
2264
|
+
remainingGaps: backstopResult.backstop.remainingGaps,
|
|
2265
|
+
});
|
|
2266
|
+
// Post backstop diagnostic comment if there were actions taken or gaps remaining
|
|
2267
|
+
const backstopComment = formatBackstopComment(backstopResult);
|
|
2268
|
+
if (backstopComment) {
|
|
2269
|
+
try {
|
|
2270
|
+
await this.client.createComment(issueId, backstopComment);
|
|
2271
|
+
}
|
|
2272
|
+
catch {
|
|
2273
|
+
// Best-effort diagnostic comment
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
// If backstop recovered the PR URL, update the session
|
|
2278
|
+
if (agent.pullRequestUrl && sessionId) {
|
|
2279
|
+
try {
|
|
2280
|
+
await this.updateSessionPullRequest(sessionId, agent.pullRequestUrl, agent);
|
|
2281
|
+
}
|
|
2282
|
+
catch {
|
|
2283
|
+
// Best-effort session update
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2216
2287
|
// Update Linear status based on work type if auto-transition is enabled
|
|
2217
2288
|
if (agent.status === 'completed' && this.config.autoTransition) {
|
|
2218
2289
|
const workType = agent.workType ?? 'development';
|
|
@@ -2271,22 +2342,14 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2271
2342
|
const incompleteCheck = checkForIncompleteWork(agent.worktreePath);
|
|
2272
2343
|
if (incompleteCheck.hasIncompleteWork) {
|
|
2273
2344
|
// Agent has uncommitted/unpushed changes — block promotion
|
|
2345
|
+
// The diagnostic comment is posted in the cleanup section AFTER the
|
|
2346
|
+
// worktree preservation is confirmed — not here, to avoid promising
|
|
2347
|
+
// preservation before it actually happens.
|
|
2274
2348
|
log?.error('Code-producing agent completed without PR and has incomplete work — blocking promotion', {
|
|
2275
2349
|
workType,
|
|
2276
2350
|
reason: incompleteCheck.reason,
|
|
2277
2351
|
details: incompleteCheck.details,
|
|
2278
2352
|
});
|
|
2279
|
-
// Post a diagnostic comment
|
|
2280
|
-
try {
|
|
2281
|
-
await this.client.createComment(issueId, `⚠️ **Agent completed but work was not persisted.**\n\n` +
|
|
2282
|
-
`The agent reported success but no PR was detected, and the worktree has ${incompleteCheck.details}.\n\n` +
|
|
2283
|
-
`**Issue status was NOT promoted** to prevent lost work from advancing through the pipeline.\n\n` +
|
|
2284
|
-
`The worktree has been preserved at \`${agent.worktreePath}\`. ` +
|
|
2285
|
-
`To recover: cd into the worktree, commit, push, and create a PR manually.`);
|
|
2286
|
-
}
|
|
2287
|
-
catch {
|
|
2288
|
-
// Best-effort comment
|
|
2289
|
-
}
|
|
2290
2353
|
// Do NOT set targetStatus — leave issue in current state
|
|
2291
2354
|
}
|
|
2292
2355
|
else {
|
|
@@ -2411,6 +2474,34 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2411
2474
|
catch {
|
|
2412
2475
|
// Best-effort - heartbeat will go stale naturally after timeout
|
|
2413
2476
|
}
|
|
2477
|
+
// Write a .preserved marker so branch conflict resolution knows not to
|
|
2478
|
+
// destroy this worktree. The marker includes context for diagnostics.
|
|
2479
|
+
try {
|
|
2480
|
+
const agentDir = resolve(agent.worktreePath, '.agent');
|
|
2481
|
+
if (!existsSync(agentDir)) {
|
|
2482
|
+
mkdirSync(agentDir, { recursive: true });
|
|
2483
|
+
}
|
|
2484
|
+
writeFileSync(resolve(agentDir, 'preserved.json'), JSON.stringify({
|
|
2485
|
+
preservedAt: new Date().toISOString(),
|
|
2486
|
+
issueId,
|
|
2487
|
+
reason: incompleteCheck.reason,
|
|
2488
|
+
details: incompleteCheck.details,
|
|
2489
|
+
}, null, 2));
|
|
2490
|
+
}
|
|
2491
|
+
catch {
|
|
2492
|
+
// Best-effort - the shouldCleanup=false flag is the primary guard
|
|
2493
|
+
}
|
|
2494
|
+
// Post diagnostic comment NOW that preservation is confirmed
|
|
2495
|
+
try {
|
|
2496
|
+
await this.client.createComment(issueId, `⚠️ **Agent completed but work was not persisted.**\n\n` +
|
|
2497
|
+
`The agent reported success but no PR was detected, and the worktree has ${incompleteCheck.details}.\n\n` +
|
|
2498
|
+
`**Issue status was NOT promoted** to prevent lost work from advancing through the pipeline.\n\n` +
|
|
2499
|
+
`The worktree has been preserved at \`${agent.worktreePath}\`. ` +
|
|
2500
|
+
`To recover: cd into the worktree, commit, push, and create a PR manually.`);
|
|
2501
|
+
}
|
|
2502
|
+
catch {
|
|
2503
|
+
// Best-effort comment
|
|
2504
|
+
}
|
|
2414
2505
|
}
|
|
2415
2506
|
else {
|
|
2416
2507
|
// No PR but also no local changes - agent may not have made any changes
|
|
@@ -2496,6 +2587,23 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2496
2587
|
catch {
|
|
2497
2588
|
// Best-effort - heartbeat will go stale naturally after timeout
|
|
2498
2589
|
}
|
|
2590
|
+
// Write a .preserved marker so branch conflict resolution knows not to
|
|
2591
|
+
// destroy this worktree
|
|
2592
|
+
try {
|
|
2593
|
+
const agentDir = resolve(agent.worktreePath, '.agent');
|
|
2594
|
+
if (!existsSync(agentDir)) {
|
|
2595
|
+
mkdirSync(agentDir, { recursive: true });
|
|
2596
|
+
}
|
|
2597
|
+
writeFileSync(resolve(agentDir, 'preserved.json'), JSON.stringify({
|
|
2598
|
+
preservedAt: new Date().toISOString(),
|
|
2599
|
+
issueId,
|
|
2600
|
+
reason: incompleteCheck.reason,
|
|
2601
|
+
details: incompleteCheck.details,
|
|
2602
|
+
}, null, 2));
|
|
2603
|
+
}
|
|
2604
|
+
catch {
|
|
2605
|
+
// Best-effort - the shouldCleanup=false flag is the primary guard
|
|
2606
|
+
}
|
|
2499
2607
|
}
|
|
2500
2608
|
}
|
|
2501
2609
|
if (shouldCleanup && agent.worktreeIdentifier) {
|
|
@@ -2622,6 +2730,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2622
2730
|
heartbeatWriter?.recordToolCall(event.toolName);
|
|
2623
2731
|
progressLogger?.logTool(event.toolName, event.input);
|
|
2624
2732
|
sessionLogger?.logToolUse(event.toolName, event.input);
|
|
2733
|
+
// Track session output signals for completion contract validation
|
|
2734
|
+
this.trackSessionOutputSignal(issueId, event.toolName, event.input);
|
|
2625
2735
|
// Intercept TodoWrite tool calls to persist todos
|
|
2626
2736
|
if (event.toolName === 'TodoWrite') {
|
|
2627
2737
|
try {
|
|
@@ -2789,6 +2899,45 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2789
2899
|
/**
|
|
2790
2900
|
* Extract GitHub PR URL from text (typically from gh pr create output)
|
|
2791
2901
|
*/
|
|
2902
|
+
/**
|
|
2903
|
+
* Track session output signals from tool calls for completion contract validation.
|
|
2904
|
+
* Detects when agents call Linear CLI or MCP tools that produce required outputs.
|
|
2905
|
+
*/
|
|
2906
|
+
trackSessionOutputSignal(issueId, toolName, input) {
|
|
2907
|
+
let flags = this.sessionOutputFlags.get(issueId);
|
|
2908
|
+
if (!flags) {
|
|
2909
|
+
flags = { commentPosted: false, issueUpdated: false, subIssuesCreated: false };
|
|
2910
|
+
this.sessionOutputFlags.set(issueId, flags);
|
|
2911
|
+
}
|
|
2912
|
+
// Detect comment creation (CLI via Bash or MCP tool)
|
|
2913
|
+
if (toolName === 'af_linear_create_comment' ||
|
|
2914
|
+
toolName === 'mcp__af-linear__af_linear_create_comment') {
|
|
2915
|
+
flags.commentPosted = true;
|
|
2916
|
+
}
|
|
2917
|
+
// Detect issue update (CLI via Bash or MCP tool)
|
|
2918
|
+
if (toolName === 'af_linear_update_issue' ||
|
|
2919
|
+
toolName === 'mcp__af-linear__af_linear_update_issue') {
|
|
2920
|
+
flags.issueUpdated = true;
|
|
2921
|
+
}
|
|
2922
|
+
// Detect issue creation (CLI via Bash or MCP tool)
|
|
2923
|
+
if (toolName === 'af_linear_create_issue' ||
|
|
2924
|
+
toolName === 'mcp__af-linear__af_linear_create_issue') {
|
|
2925
|
+
flags.subIssuesCreated = true;
|
|
2926
|
+
}
|
|
2927
|
+
// Detect Bash tool calls that invoke the Linear CLI
|
|
2928
|
+
if (toolName === 'Bash') {
|
|
2929
|
+
const command = typeof input?.command === 'string' ? input.command : '';
|
|
2930
|
+
if (command.includes('af-linear create-comment') || command.includes('af-linear create_comment')) {
|
|
2931
|
+
flags.commentPosted = true;
|
|
2932
|
+
}
|
|
2933
|
+
if (command.includes('af-linear update-issue') || command.includes('af-linear update_issue')) {
|
|
2934
|
+
flags.issueUpdated = true;
|
|
2935
|
+
}
|
|
2936
|
+
if (command.includes('af-linear create-issue') || command.includes('af-linear create_issue')) {
|
|
2937
|
+
flags.subIssuesCreated = true;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2792
2941
|
extractPullRequestUrl(text) {
|
|
2793
2942
|
// GitHub PR URL pattern: https://github.com/owner/repo/pull/123
|
|
2794
2943
|
const prUrlPattern = /https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/g;
|
|
@@ -2924,6 +3073,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2924
3073
|
progressLogger.stop();
|
|
2925
3074
|
this.progressLoggers.delete(issueId);
|
|
2926
3075
|
}
|
|
3076
|
+
// Cleanup session output flags
|
|
3077
|
+
this.sessionOutputFlags.delete(issueId);
|
|
2927
3078
|
// Persist and cleanup context manager
|
|
2928
3079
|
const contextManager = this.contextManagers.get(issueId);
|
|
2929
3080
|
if (contextManager) {
|
|
@@ -3650,7 +3801,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3650
3801
|
workType: workType ?? 'development',
|
|
3651
3802
|
});
|
|
3652
3803
|
// Create in-process tool servers from registered plugins
|
|
3653
|
-
const
|
|
3804
|
+
const toolServers = spawnProviderName === 'claude'
|
|
3654
3805
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
3655
3806
|
: undefined;
|
|
3656
3807
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -3665,7 +3816,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3665
3816
|
abortController,
|
|
3666
3817
|
autonomous: true,
|
|
3667
3818
|
sandboxEnabled: this.config.sandboxEnabled,
|
|
3668
|
-
mcpServers,
|
|
3819
|
+
mcpServers: toolServers?.servers,
|
|
3820
|
+
mcpToolNames: toolServers?.toolNames,
|
|
3669
3821
|
maxTurns,
|
|
3670
3822
|
onProcessSpawned: (pid) => {
|
|
3671
3823
|
agent.pid = pid;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Backstop
|
|
3
|
+
*
|
|
4
|
+
* Deterministic post-session recovery that runs after every agent session.
|
|
5
|
+
* Validates the session's outputs against the work type's completion contract,
|
|
6
|
+
* then takes backstop actions for any recoverable gaps.
|
|
7
|
+
*
|
|
8
|
+
* This is provider-agnostic — it operates on the worktree and GitHub API,
|
|
9
|
+
* not on the agent session. Every provider gets the same backstop.
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* 1. Collect session outputs (git state, PR detection, work result markers)
|
|
13
|
+
* 2. Validate against the completion contract
|
|
14
|
+
* 3. Run backstop actions for recoverable fields (push, create PR)
|
|
15
|
+
* 4. Return structured result for the orchestrator to act on
|
|
16
|
+
*/
|
|
17
|
+
import type { AgentProcess } from './types.js';
|
|
18
|
+
import type { BackstopResult, CompletionContract, CompletionValidationResult, SessionOutputs } from './completion-contracts.js';
|
|
19
|
+
/** Context needed to collect session outputs */
|
|
20
|
+
export interface SessionContext {
|
|
21
|
+
agent: AgentProcess;
|
|
22
|
+
/** Whether the agent posted at least one comment to the issue */
|
|
23
|
+
commentPosted: boolean;
|
|
24
|
+
/** Whether the agent updated the issue description */
|
|
25
|
+
issueUpdated: boolean;
|
|
26
|
+
/** Whether the agent created sub-issues */
|
|
27
|
+
subIssuesCreated: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Collect structured outputs from the completed session.
|
|
31
|
+
* Inspects git state, agent process data, and tracked flags.
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectSessionOutputs(ctx: SessionContext): SessionOutputs;
|
|
34
|
+
/** Options for running the backstop */
|
|
35
|
+
export interface BackstopOptions {
|
|
36
|
+
/** Skip destructive backstop actions (push, PR creation) — useful for dry-run */
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
/** PR title template. {identifier} is replaced with the issue key */
|
|
39
|
+
prTitleTemplate?: string;
|
|
40
|
+
/** PR body template. {identifier} is replaced with the issue key */
|
|
41
|
+
prBodyTemplate?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run the post-session backstop for an agent.
|
|
45
|
+
*
|
|
46
|
+
* 1. Collects session outputs
|
|
47
|
+
* 2. Validates against completion contract
|
|
48
|
+
* 3. Runs backstop actions for recoverable gaps
|
|
49
|
+
* 4. Returns structured result
|
|
50
|
+
*
|
|
51
|
+
* This function is safe to call for any work type — it returns a no-op
|
|
52
|
+
* result for work types without contracts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function runBackstop(ctx: SessionContext, options?: BackstopOptions): BackstopRunResult;
|
|
55
|
+
/** Full result of a backstop run */
|
|
56
|
+
export interface BackstopRunResult {
|
|
57
|
+
contract: CompletionContract | null;
|
|
58
|
+
outputs: SessionOutputs;
|
|
59
|
+
validation: CompletionValidationResult | null;
|
|
60
|
+
backstop: BackstopResult;
|
|
61
|
+
diagnosticMessage: string | null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format a backstop result into a diagnostic comment for the issue tracker.
|
|
65
|
+
*/
|
|
66
|
+
export declare function formatBackstopComment(result: BackstopRunResult): string | null;
|
|
67
|
+
//# sourceMappingURL=session-backstop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-backstop.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/session-backstop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,KAAK,EAEV,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACf,MAAM,2BAA2B,CAAA;AAWlC,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAA;IACtB,sDAAsD;IACtD,YAAY,EAAE,OAAO,CAAA;IACrB,2CAA2C;IAC3C,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,cAAc,CA8BzE;AAMD,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,eAAe,GACxB,iBAAiB,CAkGnB;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACnC,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,0BAA0B,GAAG,IAAI,CAAA;IAC7C,QAAQ,EAAE,cAAc,CAAA;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAmOD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA8C9E"}
|