@renseiai/agentfactory 0.8.2 → 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 +2 -2
- 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 +92 -1
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +80 -2
- package/dist/src/config/repository-config.test.js +303 -2
- 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 +26 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +111 -51
- 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/claude-provider.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.js +6 -0
- 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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
vi.mock('fs', () => ({
|
|
3
|
+
existsSync: vi.fn().mockReturnValue(true),
|
|
4
|
+
writeFileSync: vi.fn(),
|
|
5
|
+
renameSync: vi.fn(),
|
|
6
|
+
mkdirSync: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
import { writeFileSync, renameSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { createHeartbeatWriter, HeartbeatWriter, getHeartbeatIntervalFromEnv, } from './heartbeat-writer.js';
|
|
10
|
+
const defaultConfig = {
|
|
11
|
+
agentDir: '/tmp/test-agent',
|
|
12
|
+
pid: 12345,
|
|
13
|
+
intervalMs: 1000,
|
|
14
|
+
startTime: Date.now(),
|
|
15
|
+
};
|
|
16
|
+
describe('HeartbeatWriter', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.useFakeTimers();
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.useRealTimers();
|
|
24
|
+
});
|
|
25
|
+
it('createHeartbeatWriter returns a HeartbeatWriter instance', () => {
|
|
26
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
27
|
+
expect(writer).toBeInstanceOf(HeartbeatWriter);
|
|
28
|
+
});
|
|
29
|
+
it('start() writes initial heartbeat immediately', () => {
|
|
30
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
31
|
+
writer.start();
|
|
32
|
+
expect(vi.mocked(writeFileSync)).toHaveBeenCalledTimes(1);
|
|
33
|
+
expect(vi.mocked(renameSync)).toHaveBeenCalledTimes(1);
|
|
34
|
+
writer.stop();
|
|
35
|
+
});
|
|
36
|
+
it('start() creates directory if it does not exist', () => {
|
|
37
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
38
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
39
|
+
writer.start();
|
|
40
|
+
expect(vi.mocked(mkdirSync)).toHaveBeenCalledWith(expect.any(String), { recursive: true });
|
|
41
|
+
writer.stop();
|
|
42
|
+
});
|
|
43
|
+
it('start() is idempotent - calling twice does not create two intervals', () => {
|
|
44
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
45
|
+
writer.start();
|
|
46
|
+
writer.start();
|
|
47
|
+
// Only the initial heartbeat from the first start() call
|
|
48
|
+
expect(vi.mocked(writeFileSync)).toHaveBeenCalledTimes(1);
|
|
49
|
+
// Advance by one interval - should only get one additional write
|
|
50
|
+
vi.advanceTimersByTime(defaultConfig.intervalMs);
|
|
51
|
+
expect(vi.mocked(writeFileSync)).toHaveBeenCalledTimes(2);
|
|
52
|
+
writer.stop();
|
|
53
|
+
});
|
|
54
|
+
it('stop() clears the interval', () => {
|
|
55
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
56
|
+
writer.start();
|
|
57
|
+
// Initial write
|
|
58
|
+
expect(vi.mocked(writeFileSync)).toHaveBeenCalledTimes(1);
|
|
59
|
+
writer.stop();
|
|
60
|
+
// Advance time - no more writes should happen
|
|
61
|
+
vi.advanceTimersByTime(defaultConfig.intervalMs * 5);
|
|
62
|
+
expect(vi.mocked(writeFileSync)).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
it('start() throws after stop() has been called', () => {
|
|
65
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
66
|
+
writer.start();
|
|
67
|
+
writer.stop();
|
|
68
|
+
expect(() => writer.start()).toThrow('HeartbeatWriter has been stopped and cannot be restarted');
|
|
69
|
+
});
|
|
70
|
+
it('updateActivity("tool_use", "Bash") increments tool call count', () => {
|
|
71
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
72
|
+
writer.start();
|
|
73
|
+
vi.mocked(writeFileSync).mockClear();
|
|
74
|
+
vi.mocked(renameSync).mockClear();
|
|
75
|
+
writer.updateActivity('tool_use', 'Bash');
|
|
76
|
+
writer.updateActivity('tool_use', 'Read');
|
|
77
|
+
// Trigger a heartbeat write to inspect the state
|
|
78
|
+
vi.advanceTimersByTime(defaultConfig.intervalMs);
|
|
79
|
+
const writeCall = vi.mocked(writeFileSync).mock.calls[0];
|
|
80
|
+
const written = JSON.parse(writeCall[1]);
|
|
81
|
+
expect(written.toolCallsCount).toBe(2);
|
|
82
|
+
writer.stop();
|
|
83
|
+
});
|
|
84
|
+
it('recordToolCall("Read") delegates to updateActivity', () => {
|
|
85
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
86
|
+
writer.start();
|
|
87
|
+
vi.mocked(writeFileSync).mockClear();
|
|
88
|
+
vi.mocked(renameSync).mockClear();
|
|
89
|
+
writer.recordToolCall('Read');
|
|
90
|
+
vi.advanceTimersByTime(defaultConfig.intervalMs);
|
|
91
|
+
const writeCall = vi.mocked(writeFileSync).mock.calls[0];
|
|
92
|
+
const written = JSON.parse(writeCall[1]);
|
|
93
|
+
expect(written.lastActivityType).toBe('tool_use');
|
|
94
|
+
expect(written.currentOperation).toBe('Read');
|
|
95
|
+
expect(written.toolCallsCount).toBe(1);
|
|
96
|
+
writer.stop();
|
|
97
|
+
});
|
|
98
|
+
it('recordThinking() sets activity type to "thinking"', () => {
|
|
99
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
100
|
+
writer.start();
|
|
101
|
+
vi.mocked(writeFileSync).mockClear();
|
|
102
|
+
vi.mocked(renameSync).mockClear();
|
|
103
|
+
writer.recordThinking();
|
|
104
|
+
vi.advanceTimersByTime(defaultConfig.intervalMs);
|
|
105
|
+
const writeCall = vi.mocked(writeFileSync).mock.calls[0];
|
|
106
|
+
const written = JSON.parse(writeCall[1]);
|
|
107
|
+
expect(written.lastActivityType).toBe('thinking');
|
|
108
|
+
writer.stop();
|
|
109
|
+
});
|
|
110
|
+
it('heartbeat writes use atomic pattern (writeFileSync to .tmp, renameSync to final)', () => {
|
|
111
|
+
const writer = createHeartbeatWriter(defaultConfig);
|
|
112
|
+
writer.start();
|
|
113
|
+
const tmpPath = vi.mocked(writeFileSync).mock.calls[0][0];
|
|
114
|
+
expect(tmpPath).toMatch(/\.tmp$/);
|
|
115
|
+
const renameArgs = vi.mocked(renameSync).mock.calls[0];
|
|
116
|
+
expect(renameArgs[0]).toBe(tmpPath);
|
|
117
|
+
expect(renameArgs[1].endsWith('heartbeat.json')).toBe(true);
|
|
118
|
+
writer.stop();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('getHeartbeatIntervalFromEnv', () => {
|
|
122
|
+
const originalEnv = process.env.AGENT_HEARTBEAT_INTERVAL_MS;
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
if (originalEnv === undefined) {
|
|
125
|
+
delete process.env.AGENT_HEARTBEAT_INTERVAL_MS;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
process.env.AGENT_HEARTBEAT_INTERVAL_MS = originalEnv;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
it('returns default 10000 when env var is not set', () => {
|
|
132
|
+
delete process.env.AGENT_HEARTBEAT_INTERVAL_MS;
|
|
133
|
+
expect(getHeartbeatIntervalFromEnv()).toBe(10000);
|
|
134
|
+
});
|
|
135
|
+
it('reads AGENT_HEARTBEAT_INTERVAL_MS env var', () => {
|
|
136
|
+
process.env.AGENT_HEARTBEAT_INTERVAL_MS = '5000';
|
|
137
|
+
expect(getHeartbeatIntervalFromEnv()).toBe(5000);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-utils.test.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/orchestrator-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getWorktreeIdentifier } from './orchestrator.js';
|
|
3
|
+
describe('getWorktreeIdentifier', () => {
|
|
4
|
+
it('returns identifier with DEV suffix for development', () => {
|
|
5
|
+
expect(getWorktreeIdentifier('SUP-123', 'development')).toBe('SUP-123-DEV');
|
|
6
|
+
});
|
|
7
|
+
it('returns identifier with QA suffix for qa', () => {
|
|
8
|
+
expect(getWorktreeIdentifier('SUP-123', 'qa')).toBe('SUP-123-QA');
|
|
9
|
+
});
|
|
10
|
+
it('returns identifier with AC suffix for acceptance', () => {
|
|
11
|
+
expect(getWorktreeIdentifier('SUP-123', 'acceptance')).toBe('SUP-123-AC');
|
|
12
|
+
});
|
|
13
|
+
it('returns identifier with COORD suffix for coordination', () => {
|
|
14
|
+
expect(getWorktreeIdentifier('SUP-123', 'coordination')).toBe('SUP-123-COORD');
|
|
15
|
+
});
|
|
16
|
+
it('returns identifier with RES suffix for research', () => {
|
|
17
|
+
expect(getWorktreeIdentifier('SUP-123', 'research')).toBe('SUP-123-RES');
|
|
18
|
+
});
|
|
19
|
+
it('returns identifier with BC suffix for backlog-creation', () => {
|
|
20
|
+
expect(getWorktreeIdentifier('SUP-123', 'backlog-creation')).toBe('SUP-123-BC');
|
|
21
|
+
});
|
|
22
|
+
it('returns identifier with INF suffix for inflight', () => {
|
|
23
|
+
expect(getWorktreeIdentifier('SUP-123', 'inflight')).toBe('SUP-123-INF');
|
|
24
|
+
});
|
|
25
|
+
it('returns identifier with REF suffix for refinement', () => {
|
|
26
|
+
expect(getWorktreeIdentifier('SUP-123', 'refinement')).toBe('SUP-123-REF');
|
|
27
|
+
});
|
|
28
|
+
it('returns identifier with QA-COORD suffix for qa-coordination', () => {
|
|
29
|
+
expect(getWorktreeIdentifier('SUP-123', 'qa-coordination')).toBe('SUP-123-QA-COORD');
|
|
30
|
+
});
|
|
31
|
+
it('returns identifier with AC-COORD suffix for acceptance-coordination', () => {
|
|
32
|
+
expect(getWorktreeIdentifier('SUP-123', 'acceptance-coordination')).toBe('SUP-123-AC-COORD');
|
|
33
|
+
});
|
|
34
|
+
it('returns identifier with REF-COORD suffix for refinement-coordination', () => {
|
|
35
|
+
expect(getWorktreeIdentifier('SUP-123', 'refinement-coordination')).toBe('SUP-123-REF-COORD');
|
|
36
|
+
});
|
|
37
|
+
it('works with different issue identifier formats', () => {
|
|
38
|
+
expect(getWorktreeIdentifier('PROJ-1', 'development')).toBe('PROJ-1-DEV');
|
|
39
|
+
expect(getWorktreeIdentifier('AB-99999', 'qa')).toBe('AB-99999-QA');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -22,6 +22,15 @@ export declare function validateGitRemote(expectedRepo: string, cwd?: string): v
|
|
|
22
22
|
* @returns Worktree identifier with suffix (e.g., "SUP-294-QA")
|
|
23
23
|
*/
|
|
24
24
|
export declare function getWorktreeIdentifier(issueIdentifier: string, workType: AgentWorkType): string;
|
|
25
|
+
/**
|
|
26
|
+
* Detect the appropriate work type for an issue based on its status,
|
|
27
|
+
* upgrading to coordination variants for parent issues with sub-issues.
|
|
28
|
+
*
|
|
29
|
+
* This prevents parent issues returning to Backlog after refinement from
|
|
30
|
+
* being dispatched as 'development' (which uses the wrong template and
|
|
31
|
+
* produces no sub-agent orchestration).
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectWorkType(statusName: string, isParent: boolean): AgentWorkType;
|
|
25
34
|
export declare class AgentOrchestrator {
|
|
26
35
|
private readonly config;
|
|
27
36
|
private readonly client;
|
|
@@ -29,6 +38,8 @@ export declare class AgentOrchestrator {
|
|
|
29
38
|
private readonly activeAgents;
|
|
30
39
|
private readonly agentHandles;
|
|
31
40
|
private provider;
|
|
41
|
+
private readonly providerCache;
|
|
42
|
+
private configProviders?;
|
|
32
43
|
private readonly agentSessions;
|
|
33
44
|
private readonly activityEmitters;
|
|
34
45
|
private readonly sessionToIssue;
|
|
@@ -39,6 +50,7 @@ export declare class AgentOrchestrator {
|
|
|
39
50
|
private readonly sessionLoggers;
|
|
40
51
|
private readonly templateRegistry;
|
|
41
52
|
private allowedProjects?;
|
|
53
|
+
private repoConfig?;
|
|
42
54
|
private projectPaths?;
|
|
43
55
|
private sharedPaths?;
|
|
44
56
|
private linearCli?;
|
|
@@ -60,6 +72,15 @@ export declare class AgentOrchestrator {
|
|
|
60
72
|
* @returns Timeout configuration with inactivity and max session values
|
|
61
73
|
*/
|
|
62
74
|
private getTimeoutConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Detect the appropriate work type for an issue, upgrading to coordination
|
|
77
|
+
* variants for parent issues that have sub-issues.
|
|
78
|
+
*
|
|
79
|
+
* This prevents parent issues returning to Backlog after refinement from
|
|
80
|
+
* being dispatched as 'development' (which uses the wrong template and
|
|
81
|
+
* produces no sub-agent orchestration).
|
|
82
|
+
*/
|
|
83
|
+
detectWorkType(issueId: string, statusName: string): Promise<AgentWorkType>;
|
|
63
84
|
/**
|
|
64
85
|
* Get backlog issues for the configured project
|
|
65
86
|
*/
|
|
@@ -188,6 +209,11 @@ export declare class AgentOrchestrator {
|
|
|
188
209
|
* @deprecated Use linkDependencies() instead. This now delegates to linkDependencies.
|
|
189
210
|
*/
|
|
190
211
|
preInstallDependencies(worktreePath: string, identifier: string): void;
|
|
212
|
+
/**
|
|
213
|
+
* Resolve the provider for a specific spawn, using the full priority cascade.
|
|
214
|
+
* Returns a cached provider instance (creating one if needed) and the resolved name.
|
|
215
|
+
*/
|
|
216
|
+
private resolveProviderForSpawn;
|
|
191
217
|
/**
|
|
192
218
|
* Spawn a Claude agent for a specific issue using the Agent SDK
|
|
193
219
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,OAAO,EAML,KAAK,aAAa,EAQnB,MAAM,+BAA+B,CAAA;AAUtC,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAElB,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,2BAA2B,EAC5B,MAAM,YAAY,CAAA;AAmBnB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CA6B1E;AAyoBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,aAAa,GACtB,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,aAAa,CAanF;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOtB;IACD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;IACpE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsC;IACnE,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmD;IACjF,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuC;IACrE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+D;IAEhG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAEhE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAE3E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAE9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAE3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyC;IAEzE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IAEvE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyB;IAE1D,OAAO,CAAC,eAAe,CAAC,CAAU;IAElC,OAAO,CAAC,UAAU,CAAC,CAAkB;IAErC,OAAO,CAAC,YAAY,CAAC,CAAwB;IAE7C,OAAO,CAAC,WAAW,CAAC,CAAU;IAE9B,OAAO,CAAC,SAAS,CAAC,CAAQ;IAE1B,OAAO,CAAC,cAAc,CAAC,CAAQ;IAE/B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAQ;IAEhC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;gBAE/B,MAAM,GAAE,kBAAuB,EAAE,MAAM,GAAE,kBAAuB;IAuH5E;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;;;;;;OAOG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjF;;OAEG;IACG,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAmGpE;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAK7B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAMpC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAyBtB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,6BAA6B;IAiIrC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;;;OAMG;IACH,cAAc,CACZ,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,aAAa,GACtB;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE;IA6JvD;;;;OAIG;IACH,cAAc,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI;IA4BhD;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAwC5B;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA4DhE;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAmC/B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAgE3B;;OAEG;IACH,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAItE;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAwB/B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY;IA8RpD;;OAEG;YACW,kBAAkB;IAgUhC;;OAEG;YACW,gBAAgB;IAqP9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAO7B;;OAEG;YACW,wBAAwB;IAiDtC;;;OAGG;YACW,qBAAqB;IA+DnC;;OAEG;IACH,OAAO,CAAC,YAAY;IA8BpB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,kBAAkB,CAAC;IA+DxC;;;;;;;;;;;;OAYG;IACG,kBAAkB,CACtB,mBAAmB,EAAE,MAAM,EAC3B,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,aAAa,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC;IA+IxB;;OAEG;IACH,eAAe,IAAI,YAAY,EAAE;IAMjC;;;;;OAKG;IACG,SAAS,CACb,OAAO,EAAE,MAAM,EACf,eAAe,UAAQ,EACvB,UAAU,GAAE,cAAc,GAAG,SAA0B,GACtD,OAAO,CAAC,eAAe,CAAC;IA4D3B;;OAEG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,UAAQ,GAAG,OAAO,CAAC,eAAe,CAAC;IAS9F;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAM9D;;;;;;OAMG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAgBzC;;;;;;;;OAQG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,iBAAiB,CAAC,EAAE,MAAM,EAC1B,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,mBAAmB,CAAC;IAqI/B;;;;;;;;;;OAUG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC;IAqD/B;;;OAGG;IACG,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,YAAY,CAAC;IAsPvF;;OAEG;IACH,OAAO,IAAI,IAAI;IAkBf;;;;;;;;;OASG;IACG,UAAU,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CA8DhF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,CAAC,EAAE,kBAAkB,EAC3B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,iBAAiB,CAEnB"}
|
|
@@ -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 } 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
|
|
@@ -695,6 +721,8 @@ export class AgentOrchestrator {
|
|
|
695
721
|
templateRegistry;
|
|
696
722
|
// Allowlisted project names from .agentfactory/config.yaml
|
|
697
723
|
allowedProjects;
|
|
724
|
+
// Full repository config from .agentfactory/config.yaml
|
|
725
|
+
repoConfig;
|
|
698
726
|
// Project-to-path mapping from .agentfactory/config.yaml (monorepo support)
|
|
699
727
|
projectPaths;
|
|
700
728
|
// Shared paths from .agentfactory/config.yaml (monorepo support)
|
|
@@ -741,9 +769,10 @@ export class AgentOrchestrator {
|
|
|
741
769
|
}
|
|
742
770
|
this.client = createLinearAgentClient({ apiKey });
|
|
743
771
|
this.events = events;
|
|
744
|
-
// Initialize agent provider —
|
|
772
|
+
// Initialize default agent provider — per-spawn resolution may override
|
|
745
773
|
const providerName = resolveProviderName({ project: config.project });
|
|
746
774
|
this.provider = config.provider ?? createProvider(providerName);
|
|
775
|
+
this.providerCache.set(this.provider.name, this.provider);
|
|
747
776
|
// Initialize template registry for configurable workflow prompts
|
|
748
777
|
try {
|
|
749
778
|
const templateDirs = [];
|
|
@@ -772,6 +801,7 @@ export class AgentOrchestrator {
|
|
|
772
801
|
if (repoRoot) {
|
|
773
802
|
const repoConfig = loadRepositoryConfig(repoRoot);
|
|
774
803
|
if (repoConfig) {
|
|
804
|
+
this.repoConfig = repoConfig;
|
|
775
805
|
// Use repository from config as fallback if not set in OrchestratorConfig
|
|
776
806
|
if (!this.config.repository && repoConfig.repository) {
|
|
777
807
|
this.config.repository = repoConfig.repository;
|
|
@@ -779,21 +809,25 @@ export class AgentOrchestrator {
|
|
|
779
809
|
}
|
|
780
810
|
// Store allowedProjects for backlog filtering
|
|
781
811
|
if (repoConfig.projectPaths) {
|
|
782
|
-
|
|
812
|
+
// Resolve projectPaths to plain path strings (handles both string and object forms)
|
|
813
|
+
this.projectPaths = Object.fromEntries(Object.entries(repoConfig.projectPaths).map(([name, value]) => [
|
|
814
|
+
name,
|
|
815
|
+
typeof value === 'string' ? value : value.path,
|
|
816
|
+
]));
|
|
783
817
|
this.sharedPaths = repoConfig.sharedPaths;
|
|
784
818
|
this.allowedProjects = Object.keys(repoConfig.projectPaths);
|
|
785
819
|
}
|
|
786
820
|
else if (repoConfig.allowedProjects) {
|
|
787
821
|
this.allowedProjects = repoConfig.allowedProjects;
|
|
788
822
|
}
|
|
789
|
-
// Store non-Node project config
|
|
823
|
+
// Store non-Node project config (repo-wide defaults)
|
|
790
824
|
if (repoConfig.linearCli) {
|
|
791
825
|
this.linearCli = repoConfig.linearCli;
|
|
792
826
|
}
|
|
793
827
|
if (repoConfig.packageManager) {
|
|
794
828
|
this.packageManager = repoConfig.packageManager;
|
|
795
829
|
}
|
|
796
|
-
// Store configurable build/test/validate commands
|
|
830
|
+
// Store configurable build/test/validate commands (repo-wide defaults)
|
|
797
831
|
if (repoConfig.buildCommand) {
|
|
798
832
|
this.buildCommand = repoConfig.buildCommand;
|
|
799
833
|
}
|
|
@@ -803,6 +837,8 @@ export class AgentOrchestrator {
|
|
|
803
837
|
if (repoConfig.validateCommand) {
|
|
804
838
|
this.validateCommand = repoConfig.validateCommand;
|
|
805
839
|
}
|
|
840
|
+
// Store providers config for per-spawn resolution
|
|
841
|
+
this.configProviders = getProvidersConfig(repoConfig);
|
|
806
842
|
}
|
|
807
843
|
}
|
|
808
844
|
}
|
|
@@ -845,6 +881,18 @@ export class AgentOrchestrator {
|
|
|
845
881
|
}
|
|
846
882
|
return baseConfig;
|
|
847
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
|
+
}
|
|
848
896
|
/**
|
|
849
897
|
* Get backlog issues for the configured project
|
|
850
898
|
*/
|
|
@@ -1584,11 +1632,33 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1584
1632
|
preInstallDependencies(worktreePath, identifier) {
|
|
1585
1633
|
this.linkDependencies(worktreePath, identifier);
|
|
1586
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
|
+
}
|
|
1587
1655
|
/**
|
|
1588
1656
|
* Spawn a Claude agent for a specific issue using the Agent SDK
|
|
1589
1657
|
*/
|
|
1590
1658
|
spawnAgent(options) {
|
|
1591
|
-
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 });
|
|
1592
1662
|
// Generate prompt based on work type, or use custom prompt if provided
|
|
1593
1663
|
// Try template registry first, fall back to hardcoded prompts
|
|
1594
1664
|
let prompt;
|
|
@@ -1596,17 +1666,21 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1596
1666
|
prompt = customPrompt;
|
|
1597
1667
|
}
|
|
1598
1668
|
else if (this.templateRegistry?.hasTemplate(workType)) {
|
|
1669
|
+
// Resolve per-project config overrides (falls back to repo-wide defaults)
|
|
1670
|
+
const perProject = projectName && this.repoConfig
|
|
1671
|
+
? getProjectConfig(this.repoConfig, projectName)
|
|
1672
|
+
: null;
|
|
1599
1673
|
const context = {
|
|
1600
1674
|
identifier,
|
|
1601
1675
|
repository: this.config.repository,
|
|
1602
|
-
projectPath: this.projectPaths?.[projectName ?? ''],
|
|
1676
|
+
projectPath: perProject?.path ?? this.projectPaths?.[projectName ?? ''],
|
|
1603
1677
|
sharedPaths: this.sharedPaths,
|
|
1604
|
-
useToolPlugins:
|
|
1678
|
+
useToolPlugins: spawnProviderName === 'claude',
|
|
1605
1679
|
linearCli: this.linearCli ?? 'pnpm af-linear',
|
|
1606
|
-
packageManager: this.packageManager ?? 'pnpm',
|
|
1607
|
-
buildCommand: this.buildCommand,
|
|
1608
|
-
testCommand: this.testCommand,
|
|
1609
|
-
validateCommand: this.validateCommand,
|
|
1680
|
+
packageManager: perProject?.packageManager ?? this.packageManager ?? 'pnpm',
|
|
1681
|
+
buildCommand: perProject?.buildCommand ?? this.buildCommand,
|
|
1682
|
+
testCommand: perProject?.testCommand ?? this.testCommand,
|
|
1683
|
+
validateCommand: perProject?.validateCommand ?? this.validateCommand,
|
|
1610
1684
|
};
|
|
1611
1685
|
const rendered = this.templateRegistry.renderPrompt(workType, context);
|
|
1612
1686
|
prompt = rendered ?? generatePromptForWorkType(identifier, workType);
|
|
@@ -1629,6 +1703,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1629
1703
|
startedAt: now,
|
|
1630
1704
|
lastActivityAt: now, // Initialize for inactivity tracking
|
|
1631
1705
|
workType,
|
|
1706
|
+
providerName: spawnProviderName,
|
|
1632
1707
|
};
|
|
1633
1708
|
this.activeAgents.set(issueId, agent);
|
|
1634
1709
|
// Track session to issue mapping for stop signal handling
|
|
@@ -1783,9 +1858,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1783
1858
|
if (teamName) {
|
|
1784
1859
|
env.LINEAR_TEAM_NAME = teamName;
|
|
1785
1860
|
}
|
|
1786
|
-
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) });
|
|
1787
1862
|
// Create in-process tool servers from registered plugins
|
|
1788
|
-
const mcpServers =
|
|
1863
|
+
const mcpServers = spawnProviderName === 'claude'
|
|
1789
1864
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
1790
1865
|
: undefined;
|
|
1791
1866
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -1808,7 +1883,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1808
1883
|
log.info('Agent process spawned', { pid });
|
|
1809
1884
|
},
|
|
1810
1885
|
};
|
|
1811
|
-
const handle =
|
|
1886
|
+
const handle = spawnProvider.spawn(spawnConfig);
|
|
1812
1887
|
this.agentHandles.set(issueId, handle);
|
|
1813
1888
|
agent.status = 'running';
|
|
1814
1889
|
// Process the event stream in the background
|
|
@@ -2510,8 +2585,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2510
2585
|
this.events.onIssueSelected?.(issue);
|
|
2511
2586
|
console.log(`Processing: ${issue.identifier} - ${issue.title}`);
|
|
2512
2587
|
try {
|
|
2513
|
-
//
|
|
2514
|
-
const workType = '
|
|
2588
|
+
// Detect work type — parent issues with sub-issues use coordination variants
|
|
2589
|
+
const workType = await this.detectWorkType(issue.id, 'Backlog');
|
|
2515
2590
|
// Create worktree with work type suffix
|
|
2516
2591
|
const { worktreePath, worktreeIdentifier } = this.createWorktree(issue.identifier, workType);
|
|
2517
2592
|
// Link dependencies from main repo into worktree
|
|
@@ -2532,6 +2607,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2532
2607
|
workType,
|
|
2533
2608
|
teamName: issue.teamName,
|
|
2534
2609
|
projectName: issue.projectName,
|
|
2610
|
+
labels: issue.labels,
|
|
2535
2611
|
});
|
|
2536
2612
|
result.agents.push(agent);
|
|
2537
2613
|
}
|
|
@@ -2564,6 +2640,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2564
2640
|
const issueId = issue.id; // Use the actual UUID
|
|
2565
2641
|
const team = await issue.team;
|
|
2566
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);
|
|
2567
2646
|
// Resolve project name for path scoping in monorepos
|
|
2568
2647
|
let projectName;
|
|
2569
2648
|
if (this.projectPaths) {
|
|
@@ -2588,21 +2667,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2588
2667
|
if (!effectiveWorkType) {
|
|
2589
2668
|
const state = await issue.state;
|
|
2590
2669
|
const statusName = state?.name ?? 'Backlog';
|
|
2591
|
-
effectiveWorkType =
|
|
2592
|
-
console.log(`Auto-detected work type: ${effectiveWorkType} (from status: ${statusName})`);
|
|
2593
|
-
// Parent issues use coordination variants
|
|
2594
|
-
const isParent = await this.client.isParentIssue(issueId);
|
|
2595
|
-
if (isParent) {
|
|
2596
|
-
if (effectiveWorkType === 'development')
|
|
2597
|
-
effectiveWorkType = 'coordination';
|
|
2598
|
-
else if (effectiveWorkType === 'qa')
|
|
2599
|
-
effectiveWorkType = 'qa-coordination';
|
|
2600
|
-
else if (effectiveWorkType === 'acceptance')
|
|
2601
|
-
effectiveWorkType = 'acceptance-coordination';
|
|
2602
|
-
else if (effectiveWorkType === 'refinement')
|
|
2603
|
-
effectiveWorkType = 'refinement-coordination';
|
|
2604
|
-
console.log(`Upgraded to coordination work type: ${effectiveWorkType} (parent issue)`);
|
|
2605
|
-
}
|
|
2670
|
+
effectiveWorkType = await this.detectWorkType(issueId, statusName);
|
|
2606
2671
|
}
|
|
2607
2672
|
// Create isolated worktree for the agent
|
|
2608
2673
|
let worktreePath;
|
|
@@ -2656,6 +2721,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2656
2721
|
workType: recoveryWorkType,
|
|
2657
2722
|
teamName,
|
|
2658
2723
|
projectName,
|
|
2724
|
+
labels: labelNames,
|
|
2659
2725
|
});
|
|
2660
2726
|
}
|
|
2661
2727
|
}
|
|
@@ -2680,6 +2746,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2680
2746
|
prompt,
|
|
2681
2747
|
teamName,
|
|
2682
2748
|
projectName,
|
|
2749
|
+
labels: labelNames,
|
|
2683
2750
|
});
|
|
2684
2751
|
}
|
|
2685
2752
|
/**
|
|
@@ -2851,19 +2918,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2851
2918
|
// incorrect status transitions (e.g., Delivered → Started for acceptance work)
|
|
2852
2919
|
if (!workType) {
|
|
2853
2920
|
const statusName = currentStatus ?? 'Backlog';
|
|
2854
|
-
workType =
|
|
2855
|
-
// Parent issues use coordination variants
|
|
2856
|
-
const isParent = await this.client.isParentIssue(issue.id);
|
|
2857
|
-
if (isParent) {
|
|
2858
|
-
if (workType === 'development')
|
|
2859
|
-
workType = 'coordination';
|
|
2860
|
-
else if (workType === 'qa')
|
|
2861
|
-
workType = 'qa-coordination';
|
|
2862
|
-
else if (workType === 'acceptance')
|
|
2863
|
-
workType = 'acceptance-coordination';
|
|
2864
|
-
else if (workType === 'refinement')
|
|
2865
|
-
workType = 'refinement-coordination';
|
|
2866
|
-
}
|
|
2921
|
+
workType = await this.detectWorkType(issue.id, statusName);
|
|
2867
2922
|
}
|
|
2868
2923
|
// Create isolated worktree for the agent
|
|
2869
2924
|
if (WORK_TYPES_REQUIRING_WORKTREE.has(workType)) {
|
|
@@ -2914,6 +2969,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2914
2969
|
providerSessionId,
|
|
2915
2970
|
workType,
|
|
2916
2971
|
teamName,
|
|
2972
|
+
mentionContext: prompt,
|
|
2917
2973
|
});
|
|
2918
2974
|
return {
|
|
2919
2975
|
forwarded: true,
|
|
@@ -2991,7 +3047,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2991
3047
|
* If autoTransition is enabled, also transitions the issue status to the appropriate working state
|
|
2992
3048
|
*/
|
|
2993
3049
|
async spawnAgentWithResume(options) {
|
|
2994
|
-
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 });
|
|
2995
3053
|
// Create logger for this agent
|
|
2996
3054
|
const log = createLogger({ issueIdentifier: identifier });
|
|
2997
3055
|
this.agentLoggers.set(issueId, log);
|
|
@@ -3024,6 +3082,7 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3024
3082
|
startedAt: now,
|
|
3025
3083
|
lastActivityAt: now, // Initialize for inactivity tracking
|
|
3026
3084
|
workType,
|
|
3085
|
+
providerName: spawnProviderName,
|
|
3027
3086
|
};
|
|
3028
3087
|
this.activeAgents.set(issueId, agent);
|
|
3029
3088
|
// Track session to issue mapping for stop signal handling
|
|
@@ -3161,13 +3220,14 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3161
3220
|
...(teamName && { LINEAR_TEAM_NAME: teamName }),
|
|
3162
3221
|
};
|
|
3163
3222
|
log.info('Starting agent via provider', {
|
|
3164
|
-
provider:
|
|
3223
|
+
provider: spawnProviderName,
|
|
3224
|
+
source: providerSource,
|
|
3165
3225
|
cwd: worktreePath ?? 'repo-root',
|
|
3166
3226
|
resuming: !!providerSessionId,
|
|
3167
3227
|
workType: workType ?? 'development',
|
|
3168
3228
|
});
|
|
3169
3229
|
// Create in-process tool servers from registered plugins
|
|
3170
|
-
const mcpServers =
|
|
3230
|
+
const mcpServers = spawnProviderName === 'claude'
|
|
3171
3231
|
? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
|
|
3172
3232
|
: undefined;
|
|
3173
3233
|
// Coordinators need significantly more turns than standard agents
|
|
@@ -3190,8 +3250,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3190
3250
|
},
|
|
3191
3251
|
};
|
|
3192
3252
|
const handle = providerSessionId
|
|
3193
|
-
?
|
|
3194
|
-
:
|
|
3253
|
+
? spawnProvider.resume(providerSessionId, spawnConfig)
|
|
3254
|
+
: spawnProvider.spawn(spawnConfig);
|
|
3195
3255
|
this.agentHandles.set(issueId, handle);
|
|
3196
3256
|
agent.status = 'running';
|
|
3197
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":""}
|