@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.
Files changed (33) hide show
  1. package/dist/src/config/index.d.ts +2 -2
  2. package/dist/src/config/index.d.ts.map +1 -1
  3. package/dist/src/config/index.js +1 -1
  4. package/dist/src/config/repository-config.d.ts +92 -1
  5. package/dist/src/config/repository-config.d.ts.map +1 -1
  6. package/dist/src/config/repository-config.js +80 -2
  7. package/dist/src/config/repository-config.test.js +303 -2
  8. package/dist/src/orchestrator/detect-work-type.test.d.ts +2 -0
  9. package/dist/src/orchestrator/detect-work-type.test.d.ts.map +1 -0
  10. package/dist/src/orchestrator/detect-work-type.test.js +62 -0
  11. package/dist/src/orchestrator/heartbeat-writer.test.d.ts +2 -0
  12. package/dist/src/orchestrator/heartbeat-writer.test.d.ts.map +1 -0
  13. package/dist/src/orchestrator/heartbeat-writer.test.js +139 -0
  14. package/dist/src/orchestrator/orchestrator-utils.test.d.ts +2 -0
  15. package/dist/src/orchestrator/orchestrator-utils.test.d.ts.map +1 -0
  16. package/dist/src/orchestrator/orchestrator-utils.test.js +41 -0
  17. package/dist/src/orchestrator/orchestrator.d.ts +26 -0
  18. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  19. package/dist/src/orchestrator/orchestrator.js +111 -51
  20. package/dist/src/orchestrator/state-recovery.test.d.ts +2 -0
  21. package/dist/src/orchestrator/state-recovery.test.d.ts.map +1 -0
  22. package/dist/src/orchestrator/state-recovery.test.js +425 -0
  23. package/dist/src/orchestrator/types.d.ts +11 -1
  24. package/dist/src/orchestrator/types.d.ts.map +1 -1
  25. package/dist/src/providers/claude-provider.d.ts.map +1 -1
  26. package/dist/src/providers/claude-provider.js +6 -0
  27. package/dist/src/providers/index.d.ts +71 -15
  28. package/dist/src/providers/index.d.ts.map +1 -1
  29. package/dist/src/providers/index.js +156 -28
  30. package/dist/src/providers/index.test.d.ts +2 -0
  31. package/dist/src/providers/index.test.d.ts.map +1 -0
  32. package/dist/src/providers/index.test.js +225 -0
  33. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orchestrator-utils.test.d.ts.map
@@ -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":"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"}
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 — defaults to Claude, configurable via env
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
- this.projectPaths = repoConfig.projectPaths;
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: this.provider.name === 'claude',
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: this.provider.name, cwd: worktreePath ?? 'repo-root', workType, promptPreview: prompt.substring(0, 50) });
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 = this.provider.name === 'claude'
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 = this.provider.spawn(spawnConfig);
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
- // Backlog issues are always development work
2514
- const workType = 'development';
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 = STATUS_WORK_TYPE_MAP[statusName] ?? 'development';
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 = STATUS_WORK_TYPE_MAP[statusName] ?? 'development';
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: this.provider.name,
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 = this.provider.name === 'claude'
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
- ? this.provider.resume(providerSessionId, spawnConfig)
3194
- : this.provider.spawn(spawnConfig);
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state-recovery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-recovery.test.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.test.ts"],"names":[],"mappings":""}