fix: enhance error messages and documentation for team provisioning
- Updated error handling in TeamAgentToolsInstaller to provide clearer guidance on available domains, improving user experience. - Added an important note in TeamProvisioningService documentation regarding domain support, clarifying that "member" is not a valid domain. - Adjusted EditTeamDialog component for consistent styling by removing unnecessary class properties. Made-with: Cursor
This commit is contained in:
parent
0e627c4b3f
commit
faf042d640
4 changed files with 42 additions and 2 deletions
|
|
@ -1206,7 +1206,7 @@ async function main() {
|
||||||
die('Unknown process action: ' + String(action));
|
die('Unknown process action: ' + String(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
die('Unknown domain: ' + String(domain));
|
die('Unknown domain: ' + String(domain) + '. Available domains: task, kanban, review, message, process. Run with --help for usage.');
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,8 @@ function buildTeamCtlOpsInstructions(teamName: string, leadName: string): string
|
||||||
` - Do NOT split when work is inherently sequential, requires one person to keep consistent context, or the overhead would exceed the benefit.`,
|
` - Do NOT split when work is inherently sequential, requires one person to keep consistent context, or the overhead would exceed the benefit.`,
|
||||||
` - When splitting, make each task have a clear completion criterion and a single accountable owner.`,
|
` - When splitting, make each task have a clear completion criterion and a single accountable owner.`,
|
||||||
``,
|
``,
|
||||||
|
`IMPORTANT: teamctl.js only supports these domains: task, kanban, review, message, process. There is NO "member" domain — team members are managed by spawning teammates via the Task tool, not via teamctl.`,
|
||||||
|
``,
|
||||||
`Task board operations — use teamctl.js via Bash:`,
|
`Task board operations — use teamctl.js via Bash:`,
|
||||||
`- Create task: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task create --subject "..." --description "..." --owner "<actual-member-name>" --notify --from "${leadName}"`,
|
`- Create task: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task create --subject "..." --description "..." --owner "<actual-member-name>" --notify --from "${leadName}"`,
|
||||||
`- Assign/reassign owner: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task set-owner <id> <member-name> --notify --from "${leadName}"`,
|
`- Assign/reassign owner: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task set-owner <id> <member-name> --notify --from "${leadName}"`,
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ export const EditTeamDialog = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
|
<Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
|
||||||
<DialogContent className="max-w-2xl sm:max-w-md">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Edit Team</DialogTitle>
|
<DialogTitle>Edit Team</DialogTitle>
|
||||||
<DialogDescription>Change team name, description and color</DialogDescription>
|
<DialogDescription>Change team name, description and color</DialogDescription>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import { getWorktreeNavigationState } from '../utils/stateResetHelpers';
|
||||||
const logger = createLogger('teamSlice');
|
const logger = createLogger('teamSlice');
|
||||||
|
|
||||||
const TEAM_GET_DATA_TIMEOUT_MS = 30_000;
|
const TEAM_GET_DATA_TIMEOUT_MS = 30_000;
|
||||||
|
function nowIso(): string {
|
||||||
|
return new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
@ -138,6 +141,11 @@ export interface TeamSlice {
|
||||||
lastSendMessageResult: SendMessageResult | null;
|
lastSendMessageResult: SendMessageResult | null;
|
||||||
reviewActionError: string | null;
|
reviewActionError: string | null;
|
||||||
provisioningRuns: Record<string, TeamProvisioningProgress>;
|
provisioningRuns: Record<string, TeamProvisioningProgress>;
|
||||||
|
/**
|
||||||
|
* Per-team lower bound for provisioning progress timestamps.
|
||||||
|
* Used to ignore late progress events from a previous run after stop→launch.
|
||||||
|
*/
|
||||||
|
provisioningStartedAtFloorByTeam: Record<string, string>;
|
||||||
leadActivityByTeam: Record<string, LeadActivityState>;
|
leadActivityByTeam: Record<string, LeadActivityState>;
|
||||||
activeProvisioningRunId: string | null;
|
activeProvisioningRunId: string | null;
|
||||||
provisioningError: string | null;
|
provisioningError: string | null;
|
||||||
|
|
@ -231,6 +239,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
lastSendMessageResult: null,
|
lastSendMessageResult: null,
|
||||||
reviewActionError: null,
|
reviewActionError: null,
|
||||||
provisioningRuns: {},
|
provisioningRuns: {},
|
||||||
|
provisioningStartedAtFloorByTeam: {},
|
||||||
leadActivityByTeam: {},
|
leadActivityByTeam: {},
|
||||||
activeProvisioningRunId: null,
|
activeProvisioningRunId: null,
|
||||||
provisioningError: null,
|
provisioningError: null,
|
||||||
|
|
@ -746,6 +755,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
},
|
},
|
||||||
|
|
||||||
createTeam: async (request: TeamCreateRequest) => {
|
createTeam: async (request: TeamCreateRequest) => {
|
||||||
|
// Ensure provisioning progress subscription is active (defensive).
|
||||||
|
get().subscribeProvisioningProgress();
|
||||||
|
|
||||||
|
// Establish a per-team floor so late events from a previous run can't override UI.
|
||||||
|
const floor = nowIso();
|
||||||
|
set((state) => ({
|
||||||
|
provisioningStartedAtFloorByTeam: {
|
||||||
|
...state.provisioningStartedAtFloorByTeam,
|
||||||
|
[request.teamName]: floor,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// Clear stale provisioning runs for this team so the banner starts fresh
|
// Clear stale provisioning runs for this team so the banner starts fresh
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const cleaned = { ...state.provisioningRuns };
|
const cleaned = { ...state.provisioningRuns };
|
||||||
|
|
@ -783,6 +804,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
},
|
},
|
||||||
|
|
||||||
launchTeam: async (request: TeamLaunchRequest) => {
|
launchTeam: async (request: TeamLaunchRequest) => {
|
||||||
|
// Ensure provisioning progress subscription is active (defensive).
|
||||||
|
get().subscribeProvisioningProgress();
|
||||||
|
|
||||||
|
// Establish a per-team floor so late events from a previous run can't override UI.
|
||||||
|
const floor = nowIso();
|
||||||
|
set((state) => ({
|
||||||
|
provisioningStartedAtFloorByTeam: {
|
||||||
|
...state.provisioningStartedAtFloorByTeam,
|
||||||
|
[request.teamName]: floor,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// Clear stale provisioning runs for this team so the banner starts fresh
|
// Clear stale provisioning runs for this team so the banner starts fresh
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const cleaned = { ...state.provisioningRuns };
|
const cleaned = { ...state.provisioningRuns };
|
||||||
|
|
@ -826,6 +859,11 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
},
|
},
|
||||||
|
|
||||||
onProvisioningProgress: (progress: TeamProvisioningProgress) => {
|
onProvisioningProgress: (progress: TeamProvisioningProgress) => {
|
||||||
|
const floor = get().provisioningStartedAtFloorByTeam[progress.teamName];
|
||||||
|
if (floor && progress.startedAt < floor) {
|
||||||
|
// Ignore late progress from a previous run (common after stop→launch).
|
||||||
|
return;
|
||||||
|
}
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
provisioningRuns: {
|
provisioningRuns: {
|
||||||
...state.provisioningRuns,
|
...state.provisioningRuns,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue