mumucc 0.4.16 → 0.4.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mumucc",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Open-source AI coding assistant CLI with multi-model support (Anthropic, OpenAI/GPT, DeepSeek, GLM, Ollama, etc.), MCP integration, agent swarms, and out-of-the-box developer experience.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/shims/globals.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  ;(globalThis as any).MACRO = {
7
- VERSION: '0.4.16-mumucc',
7
+ VERSION: '0.4.17-mumucc',
8
8
  VERSION_CHANGELOG: '{}',
9
9
  BUILD_TIME: new Date().toISOString(),
10
10
  PACKAGE_URL: 'https://github.com/mumuxsy/mumucc',
@@ -26,6 +26,7 @@ import {
26
26
  import { assignTeammateColor } from '../../utils/swarm/teammateLayoutManager.js'
27
27
  import {
28
28
  ensureTasksDir,
29
+ clearLeaderTeamName,
29
30
  resetTaskList,
30
31
  setLeaderTeamName,
31
32
  } from '../../utils/tasks.js'
@@ -163,9 +164,19 @@ export const TeamCreateTool: Tool<InputSchema, Output> = buildTool({
163
164
  const existingTeam = appState.teamContext?.teamName
164
165
 
165
166
  if (existingTeam) {
166
- throw new Error(
167
- `Already leading team "${existingTeam}". A leader can only manage one team at a time. Use TeamDelete to end the current team before creating a new one.`,
168
- )
167
+ if (readTeamFile(existingTeam)) {
168
+ throw new Error(
169
+ `Already leading team "${existingTeam}". A leader can only manage one team at a time. Use TeamDelete to end the current team before creating a new one.`,
170
+ )
171
+ }
172
+
173
+ // Recover from a previously failed implicit spawn that populated
174
+ // AppState.teamContext before the team file was written.
175
+ clearLeaderTeamName()
176
+ setAppState(prev => ({
177
+ ...prev,
178
+ teamContext: undefined,
179
+ }))
169
180
  }
170
181
 
171
182
  // If team already exists, generate a unique name instead of failing
@@ -24,7 +24,10 @@ import { getCwd } from '../../utils/cwd.js'
24
24
  import { logForDebugging } from '../../utils/debug.js'
25
25
  import { errorMessage } from '../../utils/errors.js'
26
26
  import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
27
- import { parseUserSpecifiedModel } from '../../utils/model/model.js'
27
+ import {
28
+ getDefaultMainLoopModel,
29
+ parseUserSpecifiedModel,
30
+ } from '../../utils/model/model.js'
28
31
  import type { PermissionMode } from '../../utils/permissions/PermissionMode.js'
29
32
  import { isTmuxAvailable } from '../../utils/swarm/backends/detection.js'
30
33
  import {
@@ -52,8 +55,11 @@ import {
52
55
  import { buildInheritedEnvVars } from '../../utils/swarm/spawnUtils.js'
53
56
  import {
54
57
  readTeamFileAsync,
58
+ registerTeamForSessionCleanup,
55
59
  sanitizeAgentName,
56
60
  sanitizeName,
61
+ type TeamFile,
62
+ getTeamFilePath,
57
63
  writeTeamFileAsync,
58
64
  } from '../../utils/swarm/teamHelpers.js'
59
65
  import {
@@ -66,6 +72,7 @@ import {
66
72
  import { getHardcodedTeammateModelFallback } from '../../utils/swarm/teammateModel.js'
67
73
  import { registerTask } from '../../utils/task/framework.js'
68
74
  import { writeToMailbox } from '../../utils/teammateMailbox.js'
75
+ import { ensureTasksDir, resetTaskList, setLeaderTeamName } from '../../utils/tasks.js'
69
76
  import type { CustomAgentDefinition } from '../AgentTool/loadAgentsDir.js'
70
77
  import { isCustomAgent } from '../AgentTool/loadAgentsDir.js'
71
78
 
@@ -102,6 +109,64 @@ export function resolveTeammateModel(
102
109
  return inputModel ?? getDefaultTeammateModel(leaderModel)
103
110
  }
104
111
 
112
+ type EnsuredTeamContext = {
113
+ teamFile: TeamFile
114
+ teamFilePath: string
115
+ }
116
+
117
+ async function ensureTeamExistsForSpawn(
118
+ teamName: string,
119
+ context: ToolUseContext,
120
+ ): Promise<EnsuredTeamContext> {
121
+ const existing = await readTeamFileAsync(teamName)
122
+ if (existing) {
123
+ return {
124
+ teamFile: existing,
125
+ teamFilePath: getTeamFilePath(teamName),
126
+ }
127
+ }
128
+
129
+ const appState = context.getAppState()
130
+ const now = Date.now()
131
+ const leadAgentId = formatAgentId(TEAM_LEAD_NAME, teamName)
132
+ const leadModel = parseUserSpecifiedModel(
133
+ appState.mainLoopModelForSession ??
134
+ appState.mainLoopModel ??
135
+ getDefaultMainLoopModel(),
136
+ )
137
+ const taskListId = sanitizeName(teamName)
138
+
139
+ const teamFile: TeamFile = {
140
+ name: teamName,
141
+ createdAt: now,
142
+ leadAgentId,
143
+ leadSessionId: getSessionId(),
144
+ members: [
145
+ {
146
+ agentId: leadAgentId,
147
+ name: TEAM_LEAD_NAME,
148
+ agentType: TEAM_LEAD_NAME,
149
+ model: leadModel,
150
+ joinedAt: now,
151
+ tmuxPaneId: '',
152
+ cwd: getCwd(),
153
+ subscriptions: [],
154
+ },
155
+ ],
156
+ }
157
+
158
+ await writeTeamFileAsync(teamName, teamFile)
159
+ registerTeamForSessionCleanup(teamName)
160
+ await resetTaskList(taskListId)
161
+ await ensureTasksDir(taskListId)
162
+ setLeaderTeamName(taskListId)
163
+
164
+ return {
165
+ teamFile,
166
+ teamFilePath: getTeamFilePath(teamName),
167
+ }
168
+ }
169
+
105
170
  // ============================================================================
106
171
  // Types
107
172
  // ============================================================================
@@ -328,6 +393,8 @@ async function handleSpawnSplitPane(
328
393
  )
329
394
  }
330
395
 
396
+ const ensuredTeam = await ensureTeamExistsForSpawn(teamName, context)
397
+
331
398
  // Generate unique name if duplicate exists in team
332
399
  const uniqueName = await generateUniqueTeammateName(name, teamName)
333
400
 
@@ -450,14 +517,15 @@ async function handleSpawnSplitPane(
450
517
  const windowName = insideTmux ? 'current' : 'swarm-view'
451
518
 
452
519
  // Track the teammate in AppState's teamContext with color
453
- // If spawning without spawnTeam, set up the leader as team lead
454
520
  setAppState(prev => ({
455
521
  ...prev,
456
522
  teamContext: {
457
523
  ...prev.teamContext,
458
- teamName: teamName ?? prev.teamContext?.teamName ?? 'default',
459
- teamFilePath: prev.teamContext?.teamFilePath ?? '',
460
- leadAgentId: prev.teamContext?.leadAgentId ?? '',
524
+ teamName,
525
+ teamFilePath:
526
+ prev.teamContext?.teamFilePath || ensuredTeam.teamFilePath,
527
+ leadAgentId:
528
+ prev.teamContext?.leadAgentId || ensuredTeam.teamFile.leadAgentId,
461
529
  teammates: {
462
530
  ...(prev.teamContext?.teammates || {}),
463
531
  [teammateId]: {
@@ -488,12 +556,7 @@ async function handleSpawnSplitPane(
488
556
  })
489
557
 
490
558
  // Register agent in the team file
491
- const teamFile = await readTeamFileAsync(teamName)
492
- if (!teamFile) {
493
- throw new Error(
494
- `Team "${teamName}" does not exist. Call spawnTeam first to create the team.`,
495
- )
496
- }
559
+ const teamFile = ensuredTeam.teamFile
497
560
  teamFile.members.push({
498
561
  agentId: teammateId,
499
562
  name: sanitizedName,
@@ -568,6 +631,8 @@ async function handleSpawnSeparateWindow(
568
631
  )
569
632
  }
570
633
 
634
+ const ensuredTeam = await ensureTeamExistsForSpawn(teamName, context)
635
+
571
636
  // Generate unique name if duplicate exists in team
572
637
  const uniqueName = await generateUniqueTeammateName(name, teamName)
573
638
 
@@ -668,9 +733,11 @@ async function handleSpawnSeparateWindow(
668
733
  ...prev,
669
734
  teamContext: {
670
735
  ...prev.teamContext,
671
- teamName: teamName ?? prev.teamContext?.teamName ?? 'default',
672
- teamFilePath: prev.teamContext?.teamFilePath ?? '',
673
- leadAgentId: prev.teamContext?.leadAgentId ?? '',
736
+ teamName,
737
+ teamFilePath:
738
+ prev.teamContext?.teamFilePath || ensuredTeam.teamFilePath,
739
+ leadAgentId:
740
+ prev.teamContext?.leadAgentId || ensuredTeam.teamFile.leadAgentId,
674
741
  teammates: {
675
742
  ...(prev.teamContext?.teammates || {}),
676
743
  [teammateId]: {
@@ -702,12 +769,7 @@ async function handleSpawnSeparateWindow(
702
769
  })
703
770
 
704
771
  // Register agent in the team file
705
- const teamFile = await readTeamFileAsync(teamName)
706
- if (!teamFile) {
707
- throw new Error(
708
- `Team "${teamName}" does not exist. Call spawnTeam first to create the team.`,
709
- )
710
- }
772
+ const teamFile = ensuredTeam.teamFile
711
773
  teamFile.members.push({
712
774
  agentId: teammateId,
713
775
  name: sanitizedName,
@@ -863,6 +925,8 @@ async function handleSpawnInProcess(
863
925
  )
864
926
  }
865
927
 
928
+ const ensuredTeam = await ensureTeamExistsForSpawn(teamName, context)
929
+
866
930
  // Generate unique name if duplicate exists in team
867
931
  const uniqueName = await generateUniqueTeammateName(name, teamName)
868
932
 
@@ -940,39 +1004,22 @@ async function handleSpawnInProcess(
940
1004
  }
941
1005
 
942
1006
  // Track the teammate in AppState's teamContext
943
- // Auto-register leader if spawning without prior spawnTeam call
944
1007
  setAppState(prev => {
945
- const needsLeaderSetup = !prev.teamContext?.leadAgentId
946
- const leadAgentId = needsLeaderSetup
947
- ? formatAgentId(TEAM_LEAD_NAME, teamName)
948
- : prev.teamContext!.leadAgentId
1008
+ const leadAgentId =
1009
+ prev.teamContext?.leadAgentId || ensuredTeam.teamFile.leadAgentId
949
1010
 
950
- // Build teammates map, including leader if needed for inbox polling
951
1011
  const existingTeammates = prev.teamContext?.teammates || {}
952
- const leadEntry = needsLeaderSetup
953
- ? {
954
- [leadAgentId]: {
955
- name: TEAM_LEAD_NAME,
956
- agentType: TEAM_LEAD_NAME,
957
- color: assignTeammateColor(leadAgentId),
958
- tmuxSessionName: 'in-process',
959
- tmuxPaneId: 'leader',
960
- cwd: getCwd(),
961
- spawnedAt: Date.now(),
962
- },
963
- }
964
- : {}
965
1012
 
966
1013
  return {
967
1014
  ...prev,
968
1015
  teamContext: {
969
1016
  ...prev.teamContext,
970
- teamName: teamName ?? prev.teamContext?.teamName ?? 'default',
971
- teamFilePath: prev.teamContext?.teamFilePath ?? '',
1017
+ teamName,
1018
+ teamFilePath:
1019
+ prev.teamContext?.teamFilePath || ensuredTeam.teamFilePath,
972
1020
  leadAgentId,
973
1021
  teammates: {
974
1022
  ...existingTeammates,
975
- ...leadEntry,
976
1023
  [teammateId]: {
977
1024
  name: sanitizedName,
978
1025
  agentType: agent_type,
@@ -988,12 +1035,7 @@ async function handleSpawnInProcess(
988
1035
  })
989
1036
 
990
1037
  // Register agent in the team file
991
- const teamFile = await readTeamFileAsync(teamName)
992
- if (!teamFile) {
993
- throw new Error(
994
- `Team "${teamName}" does not exist. Call spawnTeam first to create the team.`,
995
- )
996
- }
1038
+ const teamFile = ensuredTeam.teamFile
997
1039
  teamFile.members.push({
998
1040
  agentId: teammateId,
999
1041
  name: sanitizedName,