@renseiai/agentfactory 0.8.3 → 0.8.4
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 +53 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +23 -0
- package/dist/src/config/repository-config.test.js +91 -1
- package/dist/src/orchestrator/detect-work-type.test.d.ts +2 -0
- package/dist/src/orchestrator/detect-work-type.test.d.ts.map +1 -0
- package/dist/src/orchestrator/detect-work-type.test.js +62 -0
- package/dist/src/orchestrator/heartbeat-writer.test.d.ts +2 -0
- package/dist/src/orchestrator/heartbeat-writer.test.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.test.js +139 -0
- package/dist/src/orchestrator/orchestrator-utils.test.d.ts +2 -0
- package/dist/src/orchestrator/orchestrator-utils.test.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator-utils.test.js +41 -0
- package/dist/src/orchestrator/orchestrator.d.ts +25 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +92 -43
- package/dist/src/orchestrator/state-recovery.test.d.ts +2 -0
- package/dist/src/orchestrator/state-recovery.test.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.test.js +425 -0
- package/dist/src/orchestrator/types.d.ts +11 -1
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +71 -15
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +156 -28
- package/dist/src/providers/index.test.d.ts +2 -0
- package/dist/src/providers/index.test.d.ts.map +1 -0
- package/dist/src/providers/index.test.js +225 -0
- package/package.json +3 -3
|
@@ -8,7 +8,7 @@ import { execSync } from 'child_process';
|
|
|
8
8
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'fs';
|
|
9
9
|
import { resolve, dirname } from 'path';
|
|
10
10
|
import { parse as parseDotenv } from 'dotenv';
|
|
11
|
-
import { createProvider, resolveProviderName, } from '../providers/index.js';
|
|
11
|
+
import { createProvider, resolveProviderName, resolveProviderWithSource, } from '../providers/index.js';
|
|
12
12
|
import { initializeAgentDir, writeState, updateState, writeTodos, createInitialState, checkRecovery, buildRecoveryPrompt, getHeartbeatTimeoutFromEnv, getMaxRecoveryAttemptsFromEnv, } from './state-recovery.js';
|
|
13
13
|
import { createHeartbeatWriter, getHeartbeatIntervalFromEnv } from './heartbeat-writer.js';
|
|
14
14
|
import { createProgressLogger } from './progress-logger.js';
|
|
@@ -20,7 +20,7 @@ import { createActivityEmitter } from './activity-emitter.js';
|
|
|
20
20
|
import { createApiActivityEmitter } from './api-activity-emitter.js';
|
|
21
21
|
import { createLogger } from '../logger.js';
|
|
22
22
|
import { TemplateRegistry, createToolPermissionAdapter } from '../templates/index.js';
|
|
23
|
-
import { loadRepositoryConfig, getProjectConfig } from '../config/index.js';
|
|
23
|
+
import { loadRepositoryConfig, getProjectConfig, getProvidersConfig } from '../config/index.js';
|
|
24
24
|
import { ToolRegistry, linearPlugin } from '../tools/index.js';
|
|
25
25
|
// Default inactivity timeout: 5 minutes
|
|
26
26
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 300000;
|
|
@@ -670,6 +670,30 @@ export function getWorktreeIdentifier(issueIdentifier, workType) {
|
|
|
670
670
|
const suffix = WORK_TYPE_SUFFIX[workType];
|
|
671
671
|
return `${issueIdentifier}-${suffix}`;
|
|
672
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Detect the appropriate work type for an issue based on its status,
|
|
675
|
+
* upgrading to coordination variants for parent issues with sub-issues.
|
|
676
|
+
*
|
|
677
|
+
* This prevents parent issues returning to Backlog after refinement from
|
|
678
|
+
* being dispatched as 'development' (which uses the wrong template and
|
|
679
|
+
* produces no sub-agent orchestration).
|
|
680
|
+
*/
|
|
681
|
+
export function detectWorkType(statusName, isParent) {
|
|
682
|
+
let workType = STATUS_WORK_TYPE_MAP[statusName] ?? 'development';
|
|
683
|
+
console.log(`Auto-detected work type: ${workType} (from status: ${statusName})`);
|
|
684
|
+
if (isParent) {
|
|
685
|
+
if (workType === 'development')
|
|
686
|
+
workType = 'coordination';
|
|
687
|
+
else if (workType === 'qa')
|
|
688
|
+
workType = 'qa-coordination';
|
|
689
|
+
else if (workType === 'acceptance')
|
|
690
|
+
workType = 'acceptance-coordination';
|
|
691
|
+
else if (workType === 'refinement')
|
|
692
|
+
workType = 'refinement-coordination';
|
|
693
|
+
console.log(`Upgraded to coordination work type: ${workType} (parent issue)`);
|
|
694
|
+
}
|
|
695
|
+
return workType;
|
|
696
|
+
}
|
|
673
697
|
export class AgentOrchestrator {
|
|
674
698
|
config;
|
|
675
699
|
client;
|
|
@@ -677,6 +701,8 @@ export class AgentOrchestrator {
|
|
|
677
701
|
activeAgents = new Map();
|
|
678
702
|
agentHandles = new Map();
|
|
679
703
|
provider;
|
|
704
|
+
providerCache = new Map();
|
|
705
|
+
configProviders;
|
|
680
706
|
agentSessions = new Map();
|
|
681
707
|
activityEmitters = new Map();
|
|
682
708
|
// Track session ID to issue ID mapping for stop signal handling
|
|
@@ -743,9 +769,10 @@ export class AgentOrchestrator {
|
|
|
743
769
|
}
|
|
744
770
|
this.client = createLinearAgentClient({ apiKey });
|
|
745
771
|
this.events = events;
|
|
746
|
-
// Initialize agent provider —
|
|
772
|
+
// Initialize default agent provider — per-spawn resolution may override
|
|
747
773
|
const providerName = resolveProviderName({ project: config.project });
|
|
748
774
|
this.provider = config.provider ?? createProvider(providerName);
|
|
775
|
+
this.providerCache.set(this.provider.name, this.provider);
|
|
749
776
|
// Initialize template registry for configurable workflow prompts
|
|
750
777
|
try {
|
|
751
778
|
const templateDirs = [];
|
|
@@ -810,6 +837,8 @@ export class AgentOrchestrator {
|
|
|
810
837
|
if (repoConfig.validateCommand) {
|
|
811
838
|
this.validateCommand = repoConfig.validateCommand;
|
|
812
839
|
}
|
|
840
|
+
// Store providers config for per-spawn resolution
|
|
841
|
+
this.configProviders = getProvidersConfig(repoConfig);
|
|
813
842
|
}
|
|
814
843
|
}
|
|
815
844
|
}
|
|
@@ -852,6 +881,18 @@ export class AgentOrchestrator {
|
|
|
852
881
|
}
|
|
853
882
|
return baseConfig;
|
|
854
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Detect the appropriate work type for an issue, upgrading to coordination
|
|
886
|
+
* variants for parent issues that have sub-issues.
|
|
887
|
+
*
|
|
888
|
+
* This prevents parent issues returning to Backlog after refinement from
|
|
889
|
+
* being dispatched as 'development' (which uses the wrong template and
|
|
890
|
+
* produces no sub-agent orchestration).
|
|
891
|
+
*/
|
|
892
|
+
async detectWorkType(issueId, statusName) {
|
|
893
|
+
const isParent = await this.client.isParentIssue(issueId);
|
|
894
|
+
return detectWorkType(statusName, isParent);
|
|
895
|
+
}
|
|
855
896
|
/**
|
|
856
897
|
* Get backlog issues for the configured project
|
|
857
898
|
*/
|
|
@@ -1591,11 +1632,33 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1591
1632
|
preInstallDependencies(worktreePath, identifier) {
|
|
1592
1633
|
this.linkDependencies(worktreePath, identifier);
|
|
1593
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Resolve the provider for a specific spawn, using the full priority cascade.
|
|
1637
|
+
* Returns a cached provider instance (creating one if needed) and the resolved name.
|
|
1638
|
+
*/
|
|
1639
|
+
resolveProviderForSpawn(context) {
|
|
1640
|
+
const { name, source } = resolveProviderWithSource({
|
|
1641
|
+
project: context.projectName,
|
|
1642
|
+
workType: context.workType,
|
|
1643
|
+
labels: context.labels,
|
|
1644
|
+
mentionContext: context.mentionContext,
|
|
1645
|
+
configProviders: this.configProviders,
|
|
1646
|
+
});
|
|
1647
|
+
// Return cached instance or create a new one
|
|
1648
|
+
let provider = this.providerCache.get(name);
|
|
1649
|
+
if (!provider) {
|
|
1650
|
+
provider = createProvider(name);
|
|
1651
|
+
this.providerCache.set(name, provider);
|
|
1652
|
+
}
|
|
1653
|
+
return { provider, providerName: name, source };
|
|
1654
|
+
}
|
|
1594
1655
|
/**
|
|
1595
1656
|
* Spawn a Claude agent for a specific issue using the Agent SDK
|
|
1596
1657
|
*/
|
|
1597
1658
|
spawnAgent(options) {
|
|
1598
|
-
const { issueId, identifier, worktreeIdentifier, sessionId, worktreePath, streamActivities, workType = 'development', prompt: customPrompt, teamName, projectName, } = options;
|
|
1659
|
+
const { issueId, identifier, worktreeIdentifier, sessionId, worktreePath, streamActivities, workType = 'development', prompt: customPrompt, teamName, projectName, labels, mentionContext, } = options;
|
|
1660
|
+
// Resolve provider for this specific spawn (may differ from default)
|
|
1661
|
+
const { provider: spawnProvider, providerName: spawnProviderName, source: providerSource } = this.resolveProviderForSpawn({ workType, projectName, labels, mentionContext });
|
|
1599
1662
|
// Generate prompt based on work type, or use custom prompt if provided
|
|
1600
1663
|
// Try template registry first, fall back to hardcoded prompts
|
|
1601
1664
|
let prompt;
|
|
@@ -1612,7 +1675,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1612
1675
|
repository: this.config.repository,
|
|
1613
1676
|
projectPath: perProject?.path ?? this.projectPaths?.[projectName ?? ''],
|
|
1614
1677
|
sharedPaths: this.sharedPaths,
|
|
1615
|
-
useToolPlugins:
|
|
1678
|
+
useToolPlugins: spawnProviderName === 'claude',
|
|
1616
1679
|
linearCli: this.linearCli ?? 'pnpm af-linear',
|
|
1617
1680
|
packageManager: perProject?.packageManager ?? this.packageManager ?? 'pnpm',
|
|
1618
1681
|
buildCommand: perProject?.buildCommand ?? this.buildCommand,
|
|
@@ -1640,6 +1703,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1640
1703
|
startedAt: now,
|
|
1641
1704
|
lastActivityAt: now, // Initialize for inactivity tracking
|
|
1642
1705
|
workType,
|
|
1706
|
+
providerName: spawnProviderName,
|
|
1643
1707
|
};
|
|
1644
1708
|
this.activeAgents.set(issueId, agent);
|
|
1645
1709
|
// Track session to issue mapping for stop signal handling
|
|
@@ -1794,9 +1858,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1794
1858
|
if (teamName) {
|
|
1795
1859
|
env.LINEAR_TEAM_NAME = teamName;
|
|
1796
1860
|
}
|
|
1797
|
-
log.info('Starting agent via provider', { provider:
|
|
1861
|
+
log.info('Starting agent via provider', { provider: spawnProviderName, source: providerSource, cwd: worktreePath ?? 'repo-root', workType, promptPreview: prompt.substring(0, 50) });
|
|
1798
1862
|
// Create in-process tool servers from registered plugins
|
|
1799
|
-
const mcpServers =
|
|
1863
|
+
const mcpServers = spawnProviderName === 'claude'
|
|
1800
1864
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
1801
1865
|
: undefined;
|
|
1802
1866
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -1819,7 +1883,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1819
1883
|
log.info('Agent process spawned', { pid });
|
|
1820
1884
|
},
|
|
1821
1885
|
};
|
|
1822
|
-
const handle =
|
|
1886
|
+
const handle = spawnProvider.spawn(spawnConfig);
|
|
1823
1887
|
this.agentHandles.set(issueId, handle);
|
|
1824
1888
|
agent.status = 'running';
|
|
1825
1889
|
// Process the event stream in the background
|
|
@@ -2521,8 +2585,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2521
2585
|
this.events.onIssueSelected?.(issue);
|
|
2522
2586
|
console.log(`Processing: ${issue.identifier} - ${issue.title}`);
|
|
2523
2587
|
try {
|
|
2524
|
-
//
|
|
2525
|
-
const workType = '
|
|
2588
|
+
// Detect work type — parent issues with sub-issues use coordination variants
|
|
2589
|
+
const workType = await this.detectWorkType(issue.id, 'Backlog');
|
|
2526
2590
|
// Create worktree with work type suffix
|
|
2527
2591
|
const { worktreePath, worktreeIdentifier } = this.createWorktree(issue.identifier, workType);
|
|
2528
2592
|
// Link dependencies from main repo into worktree
|
|
@@ -2543,6 +2607,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2543
2607
|
workType,
|
|
2544
2608
|
teamName: issue.teamName,
|
|
2545
2609
|
projectName: issue.projectName,
|
|
2610
|
+
labels: issue.labels,
|
|
2546
2611
|
});
|
|
2547
2612
|
result.agents.push(agent);
|
|
2548
2613
|
}
|
|
@@ -2575,6 +2640,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2575
2640
|
const issueId = issue.id; // Use the actual UUID
|
|
2576
2641
|
const team = await issue.team;
|
|
2577
2642
|
const teamName = team?.key;
|
|
2643
|
+
// Fetch labels for provider resolution
|
|
2644
|
+
const issueLabels = await issue.labels();
|
|
2645
|
+
const labelNames = issueLabels.nodes.map((l) => l.name);
|
|
2578
2646
|
// Resolve project name for path scoping in monorepos
|
|
2579
2647
|
let projectName;
|
|
2580
2648
|
if (this.projectPaths) {
|
|
@@ -2599,21 +2667,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2599
2667
|
if (!effectiveWorkType) {
|
|
2600
2668
|
const state = await issue.state;
|
|
2601
2669
|
const statusName = state?.name ?? 'Backlog';
|
|
2602
|
-
effectiveWorkType =
|
|
2603
|
-
console.log(`Auto-detected work type: ${effectiveWorkType} (from status: ${statusName})`);
|
|
2604
|
-
// Parent issues use coordination variants
|
|
2605
|
-
const isParent = await this.client.isParentIssue(issueId);
|
|
2606
|
-
if (isParent) {
|
|
2607
|
-
if (effectiveWorkType === 'development')
|
|
2608
|
-
effectiveWorkType = 'coordination';
|
|
2609
|
-
else if (effectiveWorkType === 'qa')
|
|
2610
|
-
effectiveWorkType = 'qa-coordination';
|
|
2611
|
-
else if (effectiveWorkType === 'acceptance')
|
|
2612
|
-
effectiveWorkType = 'acceptance-coordination';
|
|
2613
|
-
else if (effectiveWorkType === 'refinement')
|
|
2614
|
-
effectiveWorkType = 'refinement-coordination';
|
|
2615
|
-
console.log(`Upgraded to coordination work type: ${effectiveWorkType} (parent issue)`);
|
|
2616
|
-
}
|
|
2670
|
+
effectiveWorkType = await this.detectWorkType(issueId, statusName);
|
|
2617
2671
|
}
|
|
2618
2672
|
// Create isolated worktree for the agent
|
|
2619
2673
|
let worktreePath;
|
|
@@ -2667,6 +2721,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2667
2721
|
workType: recoveryWorkType,
|
|
2668
2722
|
teamName,
|
|
2669
2723
|
projectName,
|
|
2724
|
+
labels: labelNames,
|
|
2670
2725
|
});
|
|
2671
2726
|
}
|
|
2672
2727
|
}
|
|
@@ -2691,6 +2746,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2691
2746
|
prompt,
|
|
2692
2747
|
teamName,
|
|
2693
2748
|
projectName,
|
|
2749
|
+
labels: labelNames,
|
|
2694
2750
|
});
|
|
2695
2751
|
}
|
|
2696
2752
|
/**
|
|
@@ -2862,19 +2918,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2862
2918
|
// incorrect status transitions (e.g., Delivered → Started for acceptance work)
|
|
2863
2919
|
if (!workType) {
|
|
2864
2920
|
const statusName = currentStatus ?? 'Backlog';
|
|
2865
|
-
workType =
|
|
2866
|
-
// Parent issues use coordination variants
|
|
2867
|
-
const isParent = await this.client.isParentIssue(issue.id);
|
|
2868
|
-
if (isParent) {
|
|
2869
|
-
if (workType === 'development')
|
|
2870
|
-
workType = 'coordination';
|
|
2871
|
-
else if (workType === 'qa')
|
|
2872
|
-
workType = 'qa-coordination';
|
|
2873
|
-
else if (workType === 'acceptance')
|
|
2874
|
-
workType = 'acceptance-coordination';
|
|
2875
|
-
else if (workType === 'refinement')
|
|
2876
|
-
workType = 'refinement-coordination';
|
|
2877
|
-
}
|
|
2921
|
+
workType = await this.detectWorkType(issue.id, statusName);
|
|
2878
2922
|
}
|
|
2879
2923
|
// Create isolated worktree for the agent
|
|
2880
2924
|
if (WORK_TYPES_REQUIRING_WORKTREE.has(workType)) {
|
|
@@ -2925,6 +2969,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2925
2969
|
providerSessionId,
|
|
2926
2970
|
workType,
|
|
2927
2971
|
teamName,
|
|
2972
|
+
mentionContext: prompt,
|
|
2928
2973
|
});
|
|
2929
2974
|
return {
|
|
2930
2975
|
forwarded: true,
|
|
@@ -3002,7 +3047,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3002
3047
|
* If autoTransition is enabled, also transitions the issue status to the appropriate working state
|
|
3003
3048
|
*/
|
|
3004
3049
|
async spawnAgentWithResume(options) {
|
|
3005
|
-
const { issueId, identifier, worktreeIdentifier, sessionId, worktreePath, prompt, providerSessionId, workType, teamName } = options;
|
|
3050
|
+
const { issueId, identifier, worktreeIdentifier, sessionId, worktreePath, prompt, providerSessionId, workType, teamName, labels, mentionContext } = options;
|
|
3051
|
+
// Resolve provider for this specific spawn (may differ from default)
|
|
3052
|
+
const { provider: spawnProvider, providerName: spawnProviderName, source: providerSource } = this.resolveProviderForSpawn({ workType, projectName: options.projectName, labels, mentionContext });
|
|
3006
3053
|
// Create logger for this agent
|
|
3007
3054
|
const log = createLogger({ issueIdentifier: identifier });
|
|
3008
3055
|
this.agentLoggers.set(issueId, log);
|
|
@@ -3035,6 +3082,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3035
3082
|
startedAt: now,
|
|
3036
3083
|
lastActivityAt: now, // Initialize for inactivity tracking
|
|
3037
3084
|
workType,
|
|
3085
|
+
providerName: spawnProviderName,
|
|
3038
3086
|
};
|
|
3039
3087
|
this.activeAgents.set(issueId, agent);
|
|
3040
3088
|
// Track session to issue mapping for stop signal handling
|
|
@@ -3172,13 +3220,14 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3172
3220
|
...(teamName && { LINEAR_TEAM_NAME: teamName }),
|
|
3173
3221
|
};
|
|
3174
3222
|
log.info('Starting agent via provider', {
|
|
3175
|
-
provider:
|
|
3223
|
+
provider: spawnProviderName,
|
|
3224
|
+
source: providerSource,
|
|
3176
3225
|
cwd: worktreePath ?? 'repo-root',
|
|
3177
3226
|
resuming: !!providerSessionId,
|
|
3178
3227
|
workType: workType ?? 'development',
|
|
3179
3228
|
});
|
|
3180
3229
|
// Create in-process tool servers from registered plugins
|
|
3181
|
-
const mcpServers =
|
|
3230
|
+
const mcpServers = spawnProviderName === 'claude'
|
|
3182
3231
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
3183
3232
|
: undefined;
|
|
3184
3233
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -3201,8 +3250,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3201
3250
|
},
|
|
3202
3251
|
};
|
|
3203
3252
|
const handle = providerSessionId
|
|
3204
|
-
?
|
|
3205
|
-
:
|
|
3253
|
+
? spawnProvider.resume(providerSessionId, spawnConfig)
|
|
3254
|
+
: spawnProvider.spawn(spawnConfig);
|
|
3206
3255
|
this.agentHandles.set(issueId, handle);
|
|
3207
3256
|
agent.status = 'running';
|
|
3208
3257
|
// Process the event stream in the background
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-recovery.test.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.test.ts"],"names":[],"mappings":""}
|