chati-dev 4.2.1 → 4.2.2

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.
@@ -381,24 +381,33 @@ async function handleNext(projectDir) {
381
381
  // Check for parallel group — with Agent Teams override (Article XXI)
382
382
  let action, spawnCommand = null, parallelSpawnCommand = null, parallelAgents = [];
383
383
  let teamData = null;
384
+ const teamsEnabled = isAgentTeamsEnabled(projectDir);
385
+
384
386
  if (nextInfo.isParallel && nextInfo.group.length > 1) {
385
- // When agent_teams is enabled, substitute spawn_parallel with spawn_team
386
- if (isAgentTeamsEnabled(projectDir)) {
387
- const teamType = nextInfo.group.includes('dev') ? 'build' : 'planning';
388
- const teamConfig = TEAM_CONFIGS[teamType];
387
+ // Planning Team auto-spawn: detail + architect + ux are marked parallel: true
388
+ // in agent-selector. When agent_teams enabled, swap spawn_parallel for spawn_team.
389
+ if (teamsEnabled) {
390
+ const teamConfig = TEAM_CONFIGS.planning;
389
391
  const teamId = generateTeamId(teamConfig.slug);
390
392
  action = 'spawn_team';
391
393
  parallelAgents = teamConfig.members;
392
- teamData = {
393
- team_id: teamId,
394
- team_type: teamType,
395
- members: teamConfig.members,
396
- };
394
+ teamData = { team_id: teamId, team_type: 'planning', members: teamConfig.members };
397
395
  } else {
398
396
  action = 'spawn_parallel';
399
397
  parallelAgents = nextInfo.group;
400
398
  parallelSpawnCommand = buildParallelSpawnCommand(nextInfo.group, projectDir, lastAgent, modelInfo.provider, 900000);
401
399
  }
400
+ } else if (nextAgent === 'dev' && teamsEnabled) {
401
+ // Build Team auto-spawn: dev + qa-implementation are NOT parallel in the
402
+ // classic sense (dev writes, qa reviews per-task), so they're not in a
403
+ // parallel group. But under Agent Teams, they coordinate via mailbox + shared
404
+ // task list, which IS the team mechanism. Spawn Build Team here when dev
405
+ // would otherwise spawn solo.
406
+ const teamConfig = TEAM_CONFIGS.build;
407
+ const teamId = generateTeamId(teamConfig.slug);
408
+ action = 'spawn_team';
409
+ parallelAgents = teamConfig.members;
410
+ teamData = { team_id: teamId, team_type: 'build', members: teamConfig.members };
402
411
  } else if (isInteractive) {
403
412
  action = 'activate_interactive';
404
413
  } else {
@@ -978,12 +987,33 @@ function generateTeamId(slug) {
978
987
  return `TM-${date}-${slug}`;
979
988
  }
980
989
 
990
+ /**
991
+ * Agent Teams require BOTH the feature flag AND a Claude provider.
992
+ *
993
+ * Why provider gate: Agent Teams uses Claude Code's native Task tool / subagent
994
+ * spawn API. Gemini and Codex have no equivalent — calling spawn_team for those
995
+ * providers would fail. We always fall back silently to spawn_parallel
996
+ * (Gemini) or spawn_autonomous (Codex) when the active provider is not claude.
997
+ */
981
998
  function isAgentTeamsEnabled(projectDir) {
982
999
  const configPath = join(projectDir, 'chati.dev', 'config.yaml');
983
1000
  if (!existsSync(configPath)) return false;
984
1001
  const raw = readFileSync(configPath, 'utf-8');
985
- const match = raw.match(/agent_teams:\s*(true|false)/);
986
- return match ? match[1] === 'true' : false;
1002
+
1003
+ // Feature flag check
1004
+ const flagMatch = raw.match(/agent_teams:\s*(true|false)/);
1005
+ const flagOn = flagMatch ? flagMatch[1] === 'true' : false;
1006
+ if (!flagOn) return false;
1007
+
1008
+ // Provider gate — Agent Teams is Claude-only.
1009
+ // Read primary_provider from session.yaml (preferred) or active_provider
1010
+ // field. Default to 'claude' if neither exists (early bootstrap).
1011
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
1012
+ if (!existsSync(sessionPath)) return true; // No session yet — assume claude
1013
+ const sessionRaw = readFileSync(sessionPath, 'utf-8');
1014
+ const providerMatch = sessionRaw.match(/^\s*(?:active_provider|primary_provider):\s*["']?(\w+)/m);
1015
+ const provider = providerMatch ? providerMatch[1] : 'claude';
1016
+ return provider === 'claude';
987
1017
  }
988
1018
 
989
1019
  async function handleSpawnTeam(projectDir, args) {