@renseiai/agentfactory 0.8.13 → 0.8.14
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 +157 -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,17 @@ export class AgentOrchestrator {
|
|
|
1287
1287
|
return false;
|
|
1288
1288
|
}
|
|
1289
1289
|
}
|
|
1290
|
+
// SAFETY GUARD 4: Never destroy an explicitly preserved worktree
|
|
1291
|
+
// Preserved worktrees contain work that the orchestrator decided to keep
|
|
1292
|
+
// for manual recovery. The next agent should reuse the branch (which still
|
|
1293
|
+
// exists after worktree removal) but NOT destroy the preserved directory.
|
|
1294
|
+
const preservedMarker = resolve(conflictPath, '.agent', 'preserved.json');
|
|
1295
|
+
if (existsSync(preservedMarker)) {
|
|
1296
|
+
console.warn(`SAFETY: Refusing to clean up ${conflictPath} — it is a preserved worktree ` +
|
|
1297
|
+
`with incomplete work. The agent that ran here exited without creating a PR. ` +
|
|
1298
|
+
`Recover manually: cd into the worktree, commit, push, and create a PR.`);
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1290
1301
|
// Check if the agent in the conflicting worktree is still alive
|
|
1291
1302
|
const recoveryInfo = checkRecovery(conflictPath, {
|
|
1292
1303
|
heartbeatTimeoutMs: getHeartbeatTimeoutFromEnv(),
|
|
@@ -1899,6 +1910,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1899
1910
|
buildCommand: perProject?.buildCommand ?? this.buildCommand,
|
|
1900
1911
|
testCommand: perProject?.testCommand ?? this.testCommand,
|
|
1901
1912
|
validateCommand: perProject?.validateCommand ?? this.validateCommand,
|
|
1913
|
+
agentBugBacklog: process.env.AGENT_BUG_BACKLOG || undefined,
|
|
1902
1914
|
};
|
|
1903
1915
|
const rendered = this.templateRegistry.renderPrompt(workType, context);
|
|
1904
1916
|
prompt = rendered ?? generatePromptForWorkType(identifier, workType);
|
|
@@ -2080,7 +2092,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2080
2092
|
}
|
|
2081
2093
|
log.info('Starting agent via provider', { provider: spawnProviderName, source: providerSource, cwd: worktreePath ?? 'repo-root', workType, promptPreview: prompt.substring(0, 50) });
|
|
2082
2094
|
// Create in-process tool servers from registered plugins
|
|
2083
|
-
const
|
|
2095
|
+
const toolServers = spawnProviderName === 'claude'
|
|
2084
2096
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
2085
2097
|
: undefined;
|
|
2086
2098
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -2096,7 +2108,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2096
2108
|
abortController,
|
|
2097
2109
|
autonomous: true,
|
|
2098
2110
|
sandboxEnabled: this.config.sandboxEnabled,
|
|
2099
|
-
mcpServers,
|
|
2111
|
+
mcpServers: toolServers?.servers,
|
|
2112
|
+
mcpToolNames: toolServers?.toolNames,
|
|
2100
2113
|
maxTurns,
|
|
2101
2114
|
onProcessSpawned: (pid) => {
|
|
2102
2115
|
agent.pid = pid;
|
|
@@ -2213,6 +2226,43 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2213
2226
|
}
|
|
2214
2227
|
}
|
|
2215
2228
|
}
|
|
2229
|
+
// --- Session Backstop: Validate completion contract and recover missing outputs ---
|
|
2230
|
+
if (agent.status === 'completed') {
|
|
2231
|
+
const outputFlags = this.sessionOutputFlags.get(issueId);
|
|
2232
|
+
const backstopCtx = {
|
|
2233
|
+
agent,
|
|
2234
|
+
commentPosted: outputFlags?.commentPosted ?? false,
|
|
2235
|
+
issueUpdated: outputFlags?.issueUpdated ?? false,
|
|
2236
|
+
subIssuesCreated: outputFlags?.subIssuesCreated ?? false,
|
|
2237
|
+
};
|
|
2238
|
+
const backstopResult = runBackstop(backstopCtx);
|
|
2239
|
+
if (backstopResult.backstop.actions.length > 0) {
|
|
2240
|
+
log?.info('Session backstop ran', {
|
|
2241
|
+
actions: backstopResult.backstop.actions.map(a => `${a.field}:${a.success ? 'ok' : 'fail'}`),
|
|
2242
|
+
fullyRecovered: backstopResult.backstop.fullyRecovered,
|
|
2243
|
+
remainingGaps: backstopResult.backstop.remainingGaps,
|
|
2244
|
+
});
|
|
2245
|
+
// Post backstop diagnostic comment if there were actions taken or gaps remaining
|
|
2246
|
+
const backstopComment = formatBackstopComment(backstopResult);
|
|
2247
|
+
if (backstopComment) {
|
|
2248
|
+
try {
|
|
2249
|
+
await this.client.createComment(issueId, backstopComment);
|
|
2250
|
+
}
|
|
2251
|
+
catch {
|
|
2252
|
+
// Best-effort diagnostic comment
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
// If backstop recovered the PR URL, update the session
|
|
2257
|
+
if (agent.pullRequestUrl && sessionId) {
|
|
2258
|
+
try {
|
|
2259
|
+
await this.updateSessionPullRequest(sessionId, agent.pullRequestUrl, agent);
|
|
2260
|
+
}
|
|
2261
|
+
catch {
|
|
2262
|
+
// Best-effort session update
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2216
2266
|
// Update Linear status based on work type if auto-transition is enabled
|
|
2217
2267
|
if (agent.status === 'completed' && this.config.autoTransition) {
|
|
2218
2268
|
const workType = agent.workType ?? 'development';
|
|
@@ -2271,22 +2321,14 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2271
2321
|
const incompleteCheck = checkForIncompleteWork(agent.worktreePath);
|
|
2272
2322
|
if (incompleteCheck.hasIncompleteWork) {
|
|
2273
2323
|
// Agent has uncommitted/unpushed changes — block promotion
|
|
2324
|
+
// The diagnostic comment is posted in the cleanup section AFTER the
|
|
2325
|
+
// worktree preservation is confirmed — not here, to avoid promising
|
|
2326
|
+
// preservation before it actually happens.
|
|
2274
2327
|
log?.error('Code-producing agent completed without PR and has incomplete work — blocking promotion', {
|
|
2275
2328
|
workType,
|
|
2276
2329
|
reason: incompleteCheck.reason,
|
|
2277
2330
|
details: incompleteCheck.details,
|
|
2278
2331
|
});
|
|
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
2332
|
// Do NOT set targetStatus — leave issue in current state
|
|
2291
2333
|
}
|
|
2292
2334
|
else {
|
|
@@ -2411,6 +2453,34 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2411
2453
|
catch {
|
|
2412
2454
|
// Best-effort - heartbeat will go stale naturally after timeout
|
|
2413
2455
|
}
|
|
2456
|
+
// Write a .preserved marker so branch conflict resolution knows not to
|
|
2457
|
+
// destroy this worktree. The marker includes context for diagnostics.
|
|
2458
|
+
try {
|
|
2459
|
+
const agentDir = resolve(agent.worktreePath, '.agent');
|
|
2460
|
+
if (!existsSync(agentDir)) {
|
|
2461
|
+
mkdirSync(agentDir, { recursive: true });
|
|
2462
|
+
}
|
|
2463
|
+
writeFileSync(resolve(agentDir, 'preserved.json'), JSON.stringify({
|
|
2464
|
+
preservedAt: new Date().toISOString(),
|
|
2465
|
+
issueId,
|
|
2466
|
+
reason: incompleteCheck.reason,
|
|
2467
|
+
details: incompleteCheck.details,
|
|
2468
|
+
}, null, 2));
|
|
2469
|
+
}
|
|
2470
|
+
catch {
|
|
2471
|
+
// Best-effort - the shouldCleanup=false flag is the primary guard
|
|
2472
|
+
}
|
|
2473
|
+
// Post diagnostic comment NOW that preservation is confirmed
|
|
2474
|
+
try {
|
|
2475
|
+
await this.client.createComment(issueId, `⚠️ **Agent completed but work was not persisted.**\n\n` +
|
|
2476
|
+
`The agent reported success but no PR was detected, and the worktree has ${incompleteCheck.details}.\n\n` +
|
|
2477
|
+
`**Issue status was NOT promoted** to prevent lost work from advancing through the pipeline.\n\n` +
|
|
2478
|
+
`The worktree has been preserved at \`${agent.worktreePath}\`. ` +
|
|
2479
|
+
`To recover: cd into the worktree, commit, push, and create a PR manually.`);
|
|
2480
|
+
}
|
|
2481
|
+
catch {
|
|
2482
|
+
// Best-effort comment
|
|
2483
|
+
}
|
|
2414
2484
|
}
|
|
2415
2485
|
else {
|
|
2416
2486
|
// No PR but also no local changes - agent may not have made any changes
|
|
@@ -2496,6 +2566,23 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2496
2566
|
catch {
|
|
2497
2567
|
// Best-effort - heartbeat will go stale naturally after timeout
|
|
2498
2568
|
}
|
|
2569
|
+
// Write a .preserved marker so branch conflict resolution knows not to
|
|
2570
|
+
// destroy this worktree
|
|
2571
|
+
try {
|
|
2572
|
+
const agentDir = resolve(agent.worktreePath, '.agent');
|
|
2573
|
+
if (!existsSync(agentDir)) {
|
|
2574
|
+
mkdirSync(agentDir, { recursive: true });
|
|
2575
|
+
}
|
|
2576
|
+
writeFileSync(resolve(agentDir, 'preserved.json'), JSON.stringify({
|
|
2577
|
+
preservedAt: new Date().toISOString(),
|
|
2578
|
+
issueId,
|
|
2579
|
+
reason: incompleteCheck.reason,
|
|
2580
|
+
details: incompleteCheck.details,
|
|
2581
|
+
}, null, 2));
|
|
2582
|
+
}
|
|
2583
|
+
catch {
|
|
2584
|
+
// Best-effort - the shouldCleanup=false flag is the primary guard
|
|
2585
|
+
}
|
|
2499
2586
|
}
|
|
2500
2587
|
}
|
|
2501
2588
|
if (shouldCleanup && agent.worktreeIdentifier) {
|
|
@@ -2622,6 +2709,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2622
2709
|
heartbeatWriter?.recordToolCall(event.toolName);
|
|
2623
2710
|
progressLogger?.logTool(event.toolName, event.input);
|
|
2624
2711
|
sessionLogger?.logToolUse(event.toolName, event.input);
|
|
2712
|
+
// Track session output signals for completion contract validation
|
|
2713
|
+
this.trackSessionOutputSignal(issueId, event.toolName, event.input);
|
|
2625
2714
|
// Intercept TodoWrite tool calls to persist todos
|
|
2626
2715
|
if (event.toolName === 'TodoWrite') {
|
|
2627
2716
|
try {
|
|
@@ -2789,6 +2878,45 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2789
2878
|
/**
|
|
2790
2879
|
* Extract GitHub PR URL from text (typically from gh pr create output)
|
|
2791
2880
|
*/
|
|
2881
|
+
/**
|
|
2882
|
+
* Track session output signals from tool calls for completion contract validation.
|
|
2883
|
+
* Detects when agents call Linear CLI or MCP tools that produce required outputs.
|
|
2884
|
+
*/
|
|
2885
|
+
trackSessionOutputSignal(issueId, toolName, input) {
|
|
2886
|
+
let flags = this.sessionOutputFlags.get(issueId);
|
|
2887
|
+
if (!flags) {
|
|
2888
|
+
flags = { commentPosted: false, issueUpdated: false, subIssuesCreated: false };
|
|
2889
|
+
this.sessionOutputFlags.set(issueId, flags);
|
|
2890
|
+
}
|
|
2891
|
+
// Detect comment creation (CLI via Bash or MCP tool)
|
|
2892
|
+
if (toolName === 'af_linear_create_comment' ||
|
|
2893
|
+
toolName === 'mcp__af-linear__af_linear_create_comment') {
|
|
2894
|
+
flags.commentPosted = true;
|
|
2895
|
+
}
|
|
2896
|
+
// Detect issue update (CLI via Bash or MCP tool)
|
|
2897
|
+
if (toolName === 'af_linear_update_issue' ||
|
|
2898
|
+
toolName === 'mcp__af-linear__af_linear_update_issue') {
|
|
2899
|
+
flags.issueUpdated = true;
|
|
2900
|
+
}
|
|
2901
|
+
// Detect issue creation (CLI via Bash or MCP tool)
|
|
2902
|
+
if (toolName === 'af_linear_create_issue' ||
|
|
2903
|
+
toolName === 'mcp__af-linear__af_linear_create_issue') {
|
|
2904
|
+
flags.subIssuesCreated = true;
|
|
2905
|
+
}
|
|
2906
|
+
// Detect Bash tool calls that invoke the Linear CLI
|
|
2907
|
+
if (toolName === 'Bash') {
|
|
2908
|
+
const command = typeof input?.command === 'string' ? input.command : '';
|
|
2909
|
+
if (command.includes('af-linear create-comment') || command.includes('af-linear create_comment')) {
|
|
2910
|
+
flags.commentPosted = true;
|
|
2911
|
+
}
|
|
2912
|
+
if (command.includes('af-linear update-issue') || command.includes('af-linear update_issue')) {
|
|
2913
|
+
flags.issueUpdated = true;
|
|
2914
|
+
}
|
|
2915
|
+
if (command.includes('af-linear create-issue') || command.includes('af-linear create_issue')) {
|
|
2916
|
+
flags.subIssuesCreated = true;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2792
2920
|
extractPullRequestUrl(text) {
|
|
2793
2921
|
// GitHub PR URL pattern: https://github.com/owner/repo/pull/123
|
|
2794
2922
|
const prUrlPattern = /https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/g;
|
|
@@ -2924,6 +3052,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2924
3052
|
progressLogger.stop();
|
|
2925
3053
|
this.progressLoggers.delete(issueId);
|
|
2926
3054
|
}
|
|
3055
|
+
// Cleanup session output flags
|
|
3056
|
+
this.sessionOutputFlags.delete(issueId);
|
|
2927
3057
|
// Persist and cleanup context manager
|
|
2928
3058
|
const contextManager = this.contextManagers.get(issueId);
|
|
2929
3059
|
if (contextManager) {
|
|
@@ -3650,7 +3780,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3650
3780
|
workType: workType ?? 'development',
|
|
3651
3781
|
});
|
|
3652
3782
|
// Create in-process tool servers from registered plugins
|
|
3653
|
-
const
|
|
3783
|
+
const toolServers = spawnProviderName === 'claude'
|
|
3654
3784
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
3655
3785
|
: undefined;
|
|
3656
3786
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -3665,7 +3795,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3665
3795
|
abortController,
|
|
3666
3796
|
autonomous: true,
|
|
3667
3797
|
sandboxEnabled: this.config.sandboxEnabled,
|
|
3668
|
-
mcpServers,
|
|
3798
|
+
mcpServers: toolServers?.servers,
|
|
3799
|
+
mcpToolNames: toolServers?.toolNames,
|
|
3669
3800
|
maxTurns,
|
|
3670
3801
|
onProcessSpawned: (pid) => {
|
|
3671
3802
|
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"}
|