@renseiai/agentfactory 0.8.18 → 0.8.19
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/governor/decision-engine-adapter.d.ts +43 -0
- package/dist/src/governor/decision-engine-adapter.d.ts.map +1 -0
- package/dist/src/governor/decision-engine-adapter.js +422 -0
- package/dist/src/governor/decision-engine-adapter.test.d.ts +2 -0
- package/dist/src/governor/decision-engine-adapter.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine-adapter.test.js +363 -0
- package/dist/src/governor/index.d.ts +1 -0
- package/dist/src/governor/index.d.ts.map +1 -1
- package/dist/src/governor/index.js +1 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -1
- package/dist/src/manifest/route-manifest.js +4 -0
- package/dist/src/orchestrator/orchestrator.d.ts +27 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +289 -86
- package/dist/src/providers/claude-provider.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.js +11 -0
- package/dist/src/providers/codex-app-server-provider.d.ts +201 -0
- package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-app-server-provider.js +786 -0
- package/dist/src/providers/codex-app-server-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-app-server-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-app-server-provider.test.js +529 -0
- package/dist/src/providers/codex-provider.d.ts +24 -4
- package/dist/src/providers/codex-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-provider.js +58 -6
- package/dist/src/providers/index.d.ts +1 -0
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +1 -0
- package/dist/src/routing/observation-recorder.test.js +1 -1
- package/dist/src/routing/observation-store.d.ts +15 -1
- package/dist/src/routing/observation-store.d.ts.map +1 -1
- package/dist/src/routing/observation-store.test.js +17 -11
- package/dist/src/templates/index.d.ts +2 -1
- package/dist/src/templates/index.d.ts.map +1 -1
- package/dist/src/templates/index.js +1 -0
- package/dist/src/templates/registry.d.ts +23 -0
- package/dist/src/templates/registry.d.ts.map +1 -1
- package/dist/src/templates/registry.js +80 -0
- package/dist/src/templates/schema.d.ts +31 -0
- package/dist/src/templates/schema.d.ts.map +1 -0
- package/dist/src/templates/schema.js +139 -0
- package/dist/src/templates/schema.test.d.ts +2 -0
- package/dist/src/templates/schema.test.d.ts.map +1 -0
- package/dist/src/templates/schema.test.js +215 -0
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'fs';
|
|
8
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'fs';
|
|
9
9
|
import { resolve, dirname, basename } from 'path';
|
|
10
10
|
import { parse as parseDotenv } from 'dotenv';
|
|
11
11
|
import { createProvider, resolveProviderName, resolveProviderWithSource, } from '../providers/index.js';
|
|
@@ -27,6 +27,12 @@ import { ToolRegistry } from '../tools/index.js';
|
|
|
27
27
|
import { createMergeQueueAdapter } from '../merge-queue/index.js';
|
|
28
28
|
// Default inactivity timeout: 5 minutes
|
|
29
29
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 300000;
|
|
30
|
+
// Coordination inactivity timeout: 30 minutes.
|
|
31
|
+
// Coordinators spawn foreground sub-agents via the Agent tool. During sub-agent
|
|
32
|
+
// execution the parent event stream is silent (no tool_progress events), so the
|
|
33
|
+
// standard 5-minute inactivity timeout kills coordinators prematurely. 30 minutes
|
|
34
|
+
// gives sub-agents ample time to complete complex work.
|
|
35
|
+
const COORDINATION_INACTIVITY_TIMEOUT_MS = 1800000;
|
|
30
36
|
// Default max session timeout: unlimited (undefined)
|
|
31
37
|
const DEFAULT_MAX_SESSION_TIMEOUT_MS = undefined;
|
|
32
38
|
// Env vars that Claude Code interprets for authentication/routing. If these
|
|
@@ -1064,6 +1070,20 @@ export class AgentOrchestrator {
|
|
|
1064
1070
|
maxSessionTimeoutMs: override?.maxSessionTimeoutMs ?? baseConfig.maxSessionTimeoutMs,
|
|
1065
1071
|
};
|
|
1066
1072
|
}
|
|
1073
|
+
// Coordination work types spawn foreground sub-agents via the Agent tool.
|
|
1074
|
+
// During sub-agent execution the parent event stream is silent (no
|
|
1075
|
+
// tool_progress events flow from Agent tool execution), so the standard
|
|
1076
|
+
// inactivity timeout would kill coordinators prematurely. Use a longer
|
|
1077
|
+
// default unless the user has configured a per-work-type override above.
|
|
1078
|
+
const isCoordination = workType === 'coordination' || workType === 'inflight-coordination'
|
|
1079
|
+
|| workType === 'qa-coordination' || workType === 'acceptance-coordination'
|
|
1080
|
+
|| workType === 'refinement-coordination';
|
|
1081
|
+
if (isCoordination) {
|
|
1082
|
+
return {
|
|
1083
|
+
inactivityTimeoutMs: Math.max(baseConfig.inactivityTimeoutMs, COORDINATION_INACTIVITY_TIMEOUT_MS),
|
|
1084
|
+
maxSessionTimeoutMs: baseConfig.maxSessionTimeoutMs,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1067
1087
|
return baseConfig;
|
|
1068
1088
|
}
|
|
1069
1089
|
/**
|
|
@@ -1800,18 +1820,139 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1800
1820
|
this.linkNodeModulesContents(src, dest, identifier);
|
|
1801
1821
|
}
|
|
1802
1822
|
}
|
|
1823
|
+
// Fix 5: Also scan worktree for workspaces that exist on the branch
|
|
1824
|
+
// but not in the main repo's directory listing (e.g., newly added workspaces)
|
|
1825
|
+
for (const subdir of ['apps', 'packages']) {
|
|
1826
|
+
const wtSubdir = resolve(worktreePath, subdir);
|
|
1827
|
+
if (!existsSync(wtSubdir))
|
|
1828
|
+
continue;
|
|
1829
|
+
for (const entry of readdirSync(wtSubdir)) {
|
|
1830
|
+
const src = resolve(repoRoot, subdir, entry, 'node_modules');
|
|
1831
|
+
const dest = resolve(wtSubdir, entry, 'node_modules');
|
|
1832
|
+
if (!existsSync(src))
|
|
1833
|
+
continue; // No source deps to link
|
|
1834
|
+
if (existsSync(dest))
|
|
1835
|
+
continue; // Already linked above
|
|
1836
|
+
this.linkNodeModulesContents(src, dest, identifier);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1803
1839
|
if (skipped > 0) {
|
|
1804
1840
|
console.log(`[${identifier}] Dependencies linked successfully (${skipped} workspace(s) skipped — not on this branch)`);
|
|
1805
1841
|
}
|
|
1806
1842
|
else {
|
|
1807
1843
|
console.log(`[${identifier}] Dependencies linked successfully`);
|
|
1808
1844
|
}
|
|
1845
|
+
// Verify critical symlinks are intact; if not, remove and retry once
|
|
1846
|
+
if (!this.verifyDependencyLinks(worktreePath, identifier)) {
|
|
1847
|
+
console.warn(`[${identifier}] Dependency verification failed — removing and re-linking`);
|
|
1848
|
+
this.removeWorktreeNodeModules(worktreePath);
|
|
1849
|
+
const retryDest = resolve(worktreePath, 'node_modules');
|
|
1850
|
+
this.linkNodeModulesContents(mainNodeModules, retryDest, identifier);
|
|
1851
|
+
if (!this.verifyDependencyLinks(worktreePath, identifier)) {
|
|
1852
|
+
console.warn(`[${identifier}] Verification failed after retry — falling back to install`);
|
|
1853
|
+
this.installDependencies(worktreePath, identifier);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1809
1856
|
}
|
|
1810
1857
|
catch (error) {
|
|
1811
1858
|
console.warn(`[${identifier}] Symlink failed, falling back to install:`, error instanceof Error ? error.message : String(error));
|
|
1812
1859
|
this.installDependencies(worktreePath, identifier);
|
|
1813
1860
|
}
|
|
1814
1861
|
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Verify that critical dependency symlinks are intact and resolvable.
|
|
1864
|
+
* Returns true if verification passes, false if re-linking is needed.
|
|
1865
|
+
*/
|
|
1866
|
+
verifyDependencyLinks(worktreePath, identifier) {
|
|
1867
|
+
const destRoot = resolve(worktreePath, 'node_modules');
|
|
1868
|
+
if (!existsSync(destRoot))
|
|
1869
|
+
return false;
|
|
1870
|
+
// Sentinel packages that should always be present in a Node.js project
|
|
1871
|
+
const sentinels = ['typescript'];
|
|
1872
|
+
// Also check for .modules.yaml (pnpm store metadata) if it exists in main
|
|
1873
|
+
const repoRoot = findRepoRoot(worktreePath);
|
|
1874
|
+
if (repoRoot) {
|
|
1875
|
+
const pnpmMeta = resolve(repoRoot, 'node_modules', '.modules.yaml');
|
|
1876
|
+
if (existsSync(pnpmMeta)) {
|
|
1877
|
+
sentinels.push('.modules.yaml');
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
for (const pkg of sentinels) {
|
|
1881
|
+
const pkgPath = resolve(destRoot, pkg);
|
|
1882
|
+
if (!existsSync(pkgPath)) {
|
|
1883
|
+
console.warn(`[${identifier}] Verification: missing ${pkg}`);
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
// Follow the symlink — throws if target was deleted from main repo
|
|
1887
|
+
try {
|
|
1888
|
+
statSync(pkgPath);
|
|
1889
|
+
}
|
|
1890
|
+
catch {
|
|
1891
|
+
console.warn(`[${identifier}] Verification: broken symlink for ${pkg}`);
|
|
1892
|
+
return false;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return true;
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Remove all node_modules directories from a worktree (root + per-workspace).
|
|
1899
|
+
*/
|
|
1900
|
+
removeWorktreeNodeModules(worktreePath) {
|
|
1901
|
+
const destRoot = resolve(worktreePath, 'node_modules');
|
|
1902
|
+
try {
|
|
1903
|
+
if (existsSync(destRoot)) {
|
|
1904
|
+
rmSync(destRoot, { recursive: true, force: true });
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
catch {
|
|
1908
|
+
// Ignore cleanup errors
|
|
1909
|
+
}
|
|
1910
|
+
for (const subdir of ['apps', 'packages']) {
|
|
1911
|
+
const subPath = resolve(worktreePath, subdir);
|
|
1912
|
+
if (!existsSync(subPath))
|
|
1913
|
+
continue;
|
|
1914
|
+
try {
|
|
1915
|
+
for (const entry of readdirSync(subPath)) {
|
|
1916
|
+
const nm = resolve(subPath, entry, 'node_modules');
|
|
1917
|
+
if (existsSync(nm)) {
|
|
1918
|
+
rmSync(nm, { recursive: true, force: true });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
catch {
|
|
1923
|
+
// Ignore cleanup errors
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Create or update a symlink atomically, handling EEXIST races.
|
|
1929
|
+
*
|
|
1930
|
+
* If the destination already exists and points to the correct target, this is a no-op.
|
|
1931
|
+
* If it points elsewhere or isn't a symlink, it's replaced.
|
|
1932
|
+
*/
|
|
1933
|
+
safeSymlink(src, dest) {
|
|
1934
|
+
try {
|
|
1935
|
+
symlinkSync(src, dest);
|
|
1936
|
+
}
|
|
1937
|
+
catch (error) {
|
|
1938
|
+
if (error.code === 'EEXIST') {
|
|
1939
|
+
// Verify existing symlink points to correct target
|
|
1940
|
+
try {
|
|
1941
|
+
const existing = readlinkSync(dest);
|
|
1942
|
+
if (resolve(existing) === resolve(src))
|
|
1943
|
+
return; // Already correct
|
|
1944
|
+
}
|
|
1945
|
+
catch {
|
|
1946
|
+
// Not a symlink or can't read — remove and retry
|
|
1947
|
+
}
|
|
1948
|
+
unlinkSync(dest);
|
|
1949
|
+
symlinkSync(src, dest);
|
|
1950
|
+
}
|
|
1951
|
+
else {
|
|
1952
|
+
throw error;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1815
1956
|
/**
|
|
1816
1957
|
* Create a real node_modules directory and symlink each entry from the source.
|
|
1817
1958
|
*
|
|
@@ -1819,10 +1960,11 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1819
1960
|
* resolve through the symlink and corrupt the original), we create a real
|
|
1820
1961
|
* directory and symlink each entry individually. If pnpm "recreates" this
|
|
1821
1962
|
* directory, it only destroys the worktree's symlinks — not the originals.
|
|
1963
|
+
*
|
|
1964
|
+
* Supports incremental sync: if the destination already exists, only missing
|
|
1965
|
+
* or stale entries are updated (safe for concurrent agents and phase reuse).
|
|
1822
1966
|
*/
|
|
1823
1967
|
linkNodeModulesContents(srcNodeModules, destNodeModules, identifier) {
|
|
1824
|
-
if (existsSync(destNodeModules))
|
|
1825
|
-
return;
|
|
1826
1968
|
mkdirSync(destNodeModules, { recursive: true });
|
|
1827
1969
|
for (const entry of readdirSync(srcNodeModules)) {
|
|
1828
1970
|
const srcEntry = resolve(srcNodeModules, entry);
|
|
@@ -1835,16 +1977,12 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1835
1977
|
for (const scopedEntry of readdirSync(srcEntry)) {
|
|
1836
1978
|
const srcScoped = resolve(srcEntry, scopedEntry);
|
|
1837
1979
|
const destScoped = resolve(destEntry, scopedEntry);
|
|
1838
|
-
|
|
1839
|
-
symlinkSync(srcScoped, destScoped);
|
|
1840
|
-
}
|
|
1980
|
+
this.safeSymlink(srcScoped, destScoped);
|
|
1841
1981
|
}
|
|
1842
1982
|
continue;
|
|
1843
1983
|
}
|
|
1844
1984
|
}
|
|
1845
|
-
|
|
1846
|
-
symlinkSync(srcEntry, destEntry);
|
|
1847
|
-
}
|
|
1985
|
+
this.safeSymlink(srcEntry, destEntry);
|
|
1848
1986
|
}
|
|
1849
1987
|
}
|
|
1850
1988
|
/**
|
|
@@ -1853,36 +1991,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1853
1991
|
*/
|
|
1854
1992
|
installDependencies(worktreePath, identifier) {
|
|
1855
1993
|
console.log(`[${identifier}] Installing dependencies via pnpm...`);
|
|
1856
|
-
// Remove any node_modules from a partial linkDependencies attempt
|
|
1857
|
-
|
|
1858
|
-
// (real directory with symlinked contents).
|
|
1859
|
-
const destRoot = resolve(worktreePath, 'node_modules');
|
|
1860
|
-
try {
|
|
1861
|
-
if (existsSync(destRoot)) {
|
|
1862
|
-
rmSync(destRoot, { recursive: true, force: true });
|
|
1863
|
-
console.log(`[${identifier}] Removed partial node_modules before install`);
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
catch {
|
|
1867
|
-
// Ignore cleanup errors — pnpm install may still work
|
|
1868
|
-
}
|
|
1869
|
-
// Also remove any per-workspace node_modules that were partially created
|
|
1870
|
-
for (const subdir of ['apps', 'packages']) {
|
|
1871
|
-
const subPath = resolve(worktreePath, subdir);
|
|
1872
|
-
if (!existsSync(subPath))
|
|
1873
|
-
continue;
|
|
1874
|
-
try {
|
|
1875
|
-
for (const entry of readdirSync(subPath)) {
|
|
1876
|
-
const nm = resolve(subPath, entry, 'node_modules');
|
|
1877
|
-
if (existsSync(nm)) {
|
|
1878
|
-
rmSync(nm, { recursive: true, force: true });
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
catch {
|
|
1883
|
-
// Ignore cleanup errors
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1994
|
+
// Remove any node_modules from a partial linkDependencies attempt
|
|
1995
|
+
this.removeWorktreeNodeModules(worktreePath);
|
|
1886
1996
|
// Set ORCHESTRATOR_INSTALL=1 to bypass the preinstall guard script
|
|
1887
1997
|
// that blocks pnpm install in worktrees (to prevent symlink corruption).
|
|
1888
1998
|
const installEnv = { ...process.env, ORCHESTRATOR_INSTALL: '1' };
|
|
@@ -1912,6 +2022,82 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
1912
2022
|
}
|
|
1913
2023
|
}
|
|
1914
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Sync dependencies between worktree and main repo before linking.
|
|
2027
|
+
*
|
|
2028
|
+
* When a development agent adds new packages on a branch, the lockfile in the
|
|
2029
|
+
* worktree diverges from the main repo. This method detects lockfile drift,
|
|
2030
|
+
* updates the main repo's node_modules, then re-links into the worktree.
|
|
2031
|
+
*/
|
|
2032
|
+
syncDependencies(worktreePath, identifier) {
|
|
2033
|
+
const repoRoot = findRepoRoot(worktreePath);
|
|
2034
|
+
if (!repoRoot) {
|
|
2035
|
+
this.linkDependencies(worktreePath, identifier);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const worktreeLock = resolve(worktreePath, 'pnpm-lock.yaml');
|
|
2039
|
+
const mainLock = resolve(repoRoot, 'pnpm-lock.yaml');
|
|
2040
|
+
// Detect lockfile drift: if the worktree has a lockfile that differs from main,
|
|
2041
|
+
// a dev agent added/changed dependencies on the branch
|
|
2042
|
+
let lockfileDrifted = false;
|
|
2043
|
+
if (existsSync(worktreeLock) && existsSync(mainLock)) {
|
|
2044
|
+
try {
|
|
2045
|
+
const wtContent = readFileSync(worktreeLock, 'utf-8');
|
|
2046
|
+
const mainContent = readFileSync(mainLock, 'utf-8');
|
|
2047
|
+
lockfileDrifted = wtContent !== mainContent;
|
|
2048
|
+
}
|
|
2049
|
+
catch {
|
|
2050
|
+
// If we can't read either file, proceed without sync
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
if (lockfileDrifted) {
|
|
2054
|
+
console.log(`[${identifier}] Lockfile drift detected — syncing main repo dependencies`);
|
|
2055
|
+
try {
|
|
2056
|
+
// Copy the worktree's lockfile to the main repo so install picks up new deps
|
|
2057
|
+
copyFileSync(worktreeLock, mainLock);
|
|
2058
|
+
// Also copy any changed package.json files from worktree workspaces to main
|
|
2059
|
+
for (const subdir of ['', 'apps', 'packages']) {
|
|
2060
|
+
const wtDir = subdir ? resolve(worktreePath, subdir) : worktreePath;
|
|
2061
|
+
const mainDir = subdir ? resolve(repoRoot, subdir) : repoRoot;
|
|
2062
|
+
if (subdir && !existsSync(wtDir))
|
|
2063
|
+
continue;
|
|
2064
|
+
const entries = subdir ? readdirSync(wtDir) : [''];
|
|
2065
|
+
for (const entry of entries) {
|
|
2066
|
+
const wtPkg = resolve(wtDir, entry, 'package.json');
|
|
2067
|
+
const mainPkg = resolve(mainDir, entry, 'package.json');
|
|
2068
|
+
if (!existsSync(wtPkg))
|
|
2069
|
+
continue;
|
|
2070
|
+
try {
|
|
2071
|
+
const wtPkgContent = readFileSync(wtPkg, 'utf-8');
|
|
2072
|
+
const mainPkgContent = existsSync(mainPkg) ? readFileSync(mainPkg, 'utf-8') : '';
|
|
2073
|
+
if (wtPkgContent !== mainPkgContent) {
|
|
2074
|
+
copyFileSync(wtPkg, mainPkg);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
catch {
|
|
2078
|
+
// Skip files we can't read
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
// Install in the main repo (not the worktree) to update node_modules
|
|
2083
|
+
const installEnv = { ...process.env, ORCHESTRATOR_INSTALL: '1' };
|
|
2084
|
+
execSync('pnpm install --frozen-lockfile 2>&1', {
|
|
2085
|
+
cwd: repoRoot,
|
|
2086
|
+
stdio: 'pipe',
|
|
2087
|
+
encoding: 'utf-8',
|
|
2088
|
+
timeout: 120_000,
|
|
2089
|
+
env: installEnv,
|
|
2090
|
+
});
|
|
2091
|
+
console.log(`[${identifier}] Main repo dependencies synced`);
|
|
2092
|
+
// Remove stale worktree node_modules so linkDependencies creates fresh symlinks
|
|
2093
|
+
this.removeWorktreeNodeModules(worktreePath);
|
|
2094
|
+
}
|
|
2095
|
+
catch (error) {
|
|
2096
|
+
console.warn(`[${identifier}] Dependency sync failed, proceeding with existing state:`, error instanceof Error ? error.message : String(error));
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
this.linkDependencies(worktreePath, identifier);
|
|
2100
|
+
}
|
|
1915
2101
|
/**
|
|
1916
2102
|
* @deprecated Use linkDependencies() instead. This now delegates to linkDependencies.
|
|
1917
2103
|
*/
|
|
@@ -2340,56 +2526,64 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2340
2526
|
}
|
|
2341
2527
|
}
|
|
2342
2528
|
// Update Linear status based on work type if auto-transition is enabled
|
|
2343
|
-
if (agent.status === 'completed' && this.config.autoTransition) {
|
|
2529
|
+
if ((agent.status === 'completed' || agent.status === 'failed') && this.config.autoTransition) {
|
|
2344
2530
|
const workType = agent.workType ?? 'development';
|
|
2345
2531
|
const isResultSensitive = workType === 'qa' || workType === 'acceptance' || workType === 'coordination' || workType === 'qa-coordination' || workType === 'acceptance-coordination';
|
|
2346
2532
|
let targetStatus = null;
|
|
2347
2533
|
if (isResultSensitive) {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
let workResult = parseWorkResult(agent.resultMessage, workType);
|
|
2352
|
-
if (workResult === 'unknown' && assistantTextChunks.length > 0) {
|
|
2353
|
-
const fullText = assistantTextChunks.join('\n');
|
|
2354
|
-
workResult = parseWorkResult(fullText, workType);
|
|
2355
|
-
if (workResult !== 'unknown') {
|
|
2356
|
-
log?.info('Work result found in accumulated text (not in final message)', { workResult });
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
agent.workResult = workResult;
|
|
2360
|
-
if (workResult === 'passed') {
|
|
2361
|
-
targetStatus = this.statusMappings.workTypeCompleteStatus[workType];
|
|
2362
|
-
log?.info('Work result: passed, promoting', { workType, targetStatus });
|
|
2363
|
-
}
|
|
2364
|
-
else if (workResult === 'failed') {
|
|
2534
|
+
if (agent.status === 'failed') {
|
|
2535
|
+
// Agent crashed/errored — treat as QA/acceptance failure
|
|
2536
|
+
agent.workResult = 'failed';
|
|
2365
2537
|
targetStatus = this.statusMappings.workTypeFailStatus[workType];
|
|
2366
|
-
log?.info('
|
|
2538
|
+
log?.info('Agent failed (crash/error), transitioning to fail status', { workType, targetStatus });
|
|
2367
2539
|
}
|
|
2368
2540
|
else {
|
|
2369
|
-
//
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
`- \`<!-- WORK_RESULT:passed -->\` to promote the issue\n` +
|
|
2380
|
-
`- \`<!-- WORK_RESULT:failed -->\` to record a failure\n\n` +
|
|
2381
|
-
`This usually means the agent exited early (timeout, error, or missing logic). ` +
|
|
2382
|
-
`Check the agent logs for details, then manually update the issue status or re-trigger the agent.`);
|
|
2383
|
-
log?.info('Posted diagnostic comment for unknown work result');
|
|
2541
|
+
// For QA/acceptance: parse result to decide promote vs reject.
|
|
2542
|
+
// Try the final result message first, then fall back to scanning
|
|
2543
|
+
// all accumulated assistant text (the marker may be in an earlier turn).
|
|
2544
|
+
let workResult = parseWorkResult(agent.resultMessage, workType);
|
|
2545
|
+
if (workResult === 'unknown' && assistantTextChunks.length > 0) {
|
|
2546
|
+
const fullText = assistantTextChunks.join('\n');
|
|
2547
|
+
workResult = parseWorkResult(fullText, workType);
|
|
2548
|
+
if (workResult !== 'unknown') {
|
|
2549
|
+
log?.info('Work result found in accumulated text (not in final message)', { workResult });
|
|
2550
|
+
}
|
|
2384
2551
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2552
|
+
agent.workResult = workResult;
|
|
2553
|
+
if (workResult === 'passed') {
|
|
2554
|
+
targetStatus = this.statusMappings.workTypeCompleteStatus[workType];
|
|
2555
|
+
log?.info('Work result: passed, promoting', { workType, targetStatus });
|
|
2556
|
+
}
|
|
2557
|
+
else if (workResult === 'failed') {
|
|
2558
|
+
targetStatus = this.statusMappings.workTypeFailStatus[workType];
|
|
2559
|
+
log?.info('Work result: failed, transitioning to fail status', { workType, targetStatus });
|
|
2560
|
+
}
|
|
2561
|
+
else {
|
|
2562
|
+
// unknown — safe default: don't transition
|
|
2563
|
+
log?.warn('Work result: unknown, skipping auto-transition', {
|
|
2564
|
+
workType,
|
|
2565
|
+
hasResultMessage: !!agent.resultMessage,
|
|
2388
2566
|
});
|
|
2567
|
+
// Post a diagnostic comment so the issue doesn't silently stall
|
|
2568
|
+
try {
|
|
2569
|
+
await this.client.createComment(issueId, `⚠️ Agent completed but no structured result marker was detected in the output.\n\n` +
|
|
2570
|
+
`**Issue status was NOT updated automatically.**\n\n` +
|
|
2571
|
+
`The orchestrator expected one of:\n` +
|
|
2572
|
+
`- \`<!-- WORK_RESULT:passed -->\` to promote the issue\n` +
|
|
2573
|
+
`- \`<!-- WORK_RESULT:failed -->\` to record a failure\n\n` +
|
|
2574
|
+
`This usually means the agent exited early (timeout, error, or missing logic). ` +
|
|
2575
|
+
`Check the agent logs for details, then manually update the issue status or re-trigger the agent.`);
|
|
2576
|
+
log?.info('Posted diagnostic comment for unknown work result');
|
|
2577
|
+
}
|
|
2578
|
+
catch (error) {
|
|
2579
|
+
log?.warn('Failed to post diagnostic comment for unknown work result', {
|
|
2580
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2389
2583
|
}
|
|
2390
2584
|
}
|
|
2391
2585
|
}
|
|
2392
|
-
else {
|
|
2586
|
+
else if (agent.status === 'completed') {
|
|
2393
2587
|
// Non-QA/acceptance: promote on completion, but validate code-producing work types first
|
|
2394
2588
|
const isCodeProducing = workType === 'development' || workType === 'inflight';
|
|
2395
2589
|
if (isCodeProducing && agent.worktreePath && !agent.pullRequestUrl) {
|
|
@@ -2704,7 +2898,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2704
2898
|
agent.providerSessionId = event.sessionId;
|
|
2705
2899
|
this.updateLastActivity(issueId, 'init');
|
|
2706
2900
|
// Update state with provider session ID (only for worktree-based agents)
|
|
2707
|
-
if
|
|
2901
|
+
// Skip if agent already failed — a late init event after an error would
|
|
2902
|
+
// re-persist a stale session ID, preventing fresh recovery on next attempt
|
|
2903
|
+
if (agent.worktreePath && agent.status !== 'failed') {
|
|
2708
2904
|
try {
|
|
2709
2905
|
updateState(agent.worktreePath, {
|
|
2710
2906
|
providerSessionId: event.sessionId,
|
|
@@ -2882,10 +3078,17 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
2882
3078
|
: `Agent error: ${event.errorSubtype}`;
|
|
2883
3079
|
if (agent.worktreePath) {
|
|
2884
3080
|
try {
|
|
3081
|
+
// If the error is a stale session (resume failed), clear providerSessionId
|
|
3082
|
+
// so the next recovery attempt starts fresh instead of hitting the same error
|
|
3083
|
+
const isStaleSession = errorMessage.includes('No conversation found with session ID');
|
|
2885
3084
|
updateState(agent.worktreePath, {
|
|
2886
3085
|
status: 'failed',
|
|
2887
3086
|
errorMessage,
|
|
3087
|
+
...(isStaleSession && { providerSessionId: null }),
|
|
2888
3088
|
});
|
|
3089
|
+
if (isStaleSession) {
|
|
3090
|
+
log?.info('Cleared stale providerSessionId from state — next recovery will start fresh');
|
|
3091
|
+
}
|
|
2889
3092
|
}
|
|
2890
3093
|
catch {
|
|
2891
3094
|
// Ignore state update errors
|
|
@@ -3187,8 +3390,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3187
3390
|
const workType = await this.detectWorkType(issue.id, 'Backlog');
|
|
3188
3391
|
// Create worktree with work type suffix
|
|
3189
3392
|
const { worktreePath, worktreeIdentifier } = this.createWorktree(issue.identifier, workType);
|
|
3190
|
-
//
|
|
3191
|
-
this.
|
|
3393
|
+
// Sync and link dependencies from main repo into worktree
|
|
3394
|
+
this.syncDependencies(worktreePath, issue.identifier);
|
|
3192
3395
|
const startStatus = this.statusMappings.workTypeStartStatus[workType];
|
|
3193
3396
|
// Update issue status based on work type if auto-transition is enabled
|
|
3194
3397
|
if (this.config.autoTransition && startStatus) {
|
|
@@ -3305,8 +3508,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3305
3508
|
const wt = this.createWorktree(identifier, effectiveWorkType);
|
|
3306
3509
|
worktreePath = wt.worktreePath;
|
|
3307
3510
|
worktreeIdentifier = wt.worktreeIdentifier;
|
|
3308
|
-
//
|
|
3309
|
-
this.
|
|
3511
|
+
// Sync and link dependencies from main repo into worktree
|
|
3512
|
+
this.syncDependencies(worktreePath, identifier);
|
|
3310
3513
|
// Check for existing state and potential recovery
|
|
3311
3514
|
const recoveryCheck = checkRecovery(worktreePath, {
|
|
3312
3515
|
heartbeatTimeoutMs: getHeartbeatTimeoutFromEnv(),
|
|
@@ -3559,8 +3762,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3559
3762
|
const result = this.createWorktree(identifier, workType);
|
|
3560
3763
|
worktreePath = result.worktreePath;
|
|
3561
3764
|
worktreeIdentifier = result.worktreeIdentifier;
|
|
3562
|
-
//
|
|
3563
|
-
this.
|
|
3765
|
+
// Sync and link dependencies from main repo into worktree
|
|
3766
|
+
this.syncDependencies(worktreePath, identifier);
|
|
3564
3767
|
}
|
|
3565
3768
|
}
|
|
3566
3769
|
catch (error) {
|
|
@@ -3579,8 +3782,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
|
|
|
3579
3782
|
const result = this.createWorktree(identifier, effectiveWorkType);
|
|
3580
3783
|
worktreePath = result.worktreePath;
|
|
3581
3784
|
worktreeIdentifier = result.worktreeIdentifier;
|
|
3582
|
-
//
|
|
3583
|
-
this.
|
|
3785
|
+
// Sync and link dependencies from main repo into worktree
|
|
3786
|
+
this.syncDependencies(worktreePath, identifier);
|
|
3584
3787
|
}
|
|
3585
3788
|
catch (error) {
|
|
3586
3789
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/claude-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EAEZ,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/claude-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EAEZ,MAAM,YAAY,CAAA;AA6FnB,qBAAa,cAAe,YAAW,aAAa;IAClD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAS;IACjC,QAAQ,CAAC,YAAY;;;MAGX;IAEV,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,WAAW;IAI5C,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,WAAW;IAIhE,OAAO,CAAC,YAAY;CA8KrB;AAsND;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAErD"}
|
|
@@ -26,6 +26,17 @@ const autonomousCanUseTool = async (toolName, input) => {
|
|
|
26
26
|
if (['Write', 'Edit', 'NotebookEdit'].includes(toolName)) {
|
|
27
27
|
return { behavior: 'allow', updatedInput: input };
|
|
28
28
|
}
|
|
29
|
+
// Agent tool: always force foreground execution.
|
|
30
|
+
// Coordinators that spawn sub-agents with run_in_background=true exit
|
|
31
|
+
// before sub-agents finish, orphaning work. Strip the flag so the Agent
|
|
32
|
+
// tool blocks until the sub-agent completes.
|
|
33
|
+
if (toolName === 'Agent') {
|
|
34
|
+
if (input.run_in_background) {
|
|
35
|
+
const { run_in_background: _, ...rest } = input;
|
|
36
|
+
return { behavior: 'allow', updatedInput: rest };
|
|
37
|
+
}
|
|
38
|
+
return { behavior: 'allow', updatedInput: input };
|
|
39
|
+
}
|
|
29
40
|
// Task management and planning
|
|
30
41
|
if (['Task', 'TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList',
|
|
31
42
|
'EnterPlanMode', 'ExitPlanMode', 'Skill'].includes(toolName)) {
|